machine-bridge-mcp 0.11.1 → 0.12.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.1 - 2026-07-12
4
+
5
+ ### Real local automation corrections
6
+
7
+ - Make the fixed macOS JXA helper return JSON as the top-level `osascript` result instead of using `console.log`, which real `osascript` sends to stderr. Empty helper stdout now fails explicitly rather than being interpreted as a successful empty object.
8
+ - Skip recursive macOS menu-bar/menu traversal by default so bounded inspection reaches main-window controls promptly. Add `include_menus` to inspection and actions for explicit menu automation.
9
+ - Normalize hyphens, underscores, dots, and spaces during capability ranking, and strongly boost explicitly named skills and commands. Natural-language references such as `agents progressive disclosure` now select `agents-progressive-disclosure` instead of a generic competing skill.
10
+
11
+ ### Live verification and regression coverage
12
+
13
+ - Add regression coverage for missing JXA output, menu-recursion forwarding, and punctuation-normalized Skill selection. Add an opt-in real macOS Calculator smoke path and document isolated-profile MV3 browser validation.
14
+ - Validate the source runtime with a temporary real global instruction file, an existing user Skill, a registered direct-argv command, shell execution, Calculator discovery/open/activation/inspection/click, and a real unpacked browser extension controlling a local form through inspect/fill/click/source/screenshot operations. Test files, browser profiles, temporary global configuration, tabs, and application state are removed afterward.
15
+
16
+ ## 0.12.0 - 2026-07-12
17
+
18
+ ### Locking, persistence, and process lifecycle
19
+
20
+ - Replace partial-write-prone final-path lock creation with fully written, flushed temporary files and atomic same-directory hard-link claims. Record ownership tokens and process start times, detect PID reuse, verify file identity before stale-lock removal, preserve recent malformed locks, and add bounded startup-lock waiting.
21
+ - Add a state-root maintenance lock for full uninstall, including checks in already constructed managed-job and browser managers. State roots must be disjoint from selected workspaces, including non-existent paths beneath canonicalized platform aliases; unreadable locks/jobs and ambiguous process identities now block destructive cleanup rather than being treated as inactive.
22
+ - Consolidate owner-only state, managed-job, runner, browser-pairing, and service-definition commits on one flushed atomic replacement primitive. Only successfully read invalid JSON is backed up; permission, symbolic-link, size, invalid UTF-8, and I/O failures propagate without silently reconstructing empty state.
23
+ - Persist managed-job runner process identity, protect transition/recovery locks with token and file-snapshot checks, hand recovery ownership to the runner without an unconditional delete, and keep timeout/cancellation escalation alive until resistant descendant process trees are terminated.
24
+
25
+ ### Services, uninstall, and cross-platform behavior
26
+
27
+ - Extract one fail-closed service lifecycle: stop the platform service, stop every verified workspace daemon, and remove the service definition only after all stop phases succeed. Full uninstall scans every profile and rechecks managed jobs and process locks while holding maintenance ownership.
28
+ - Normalize launchd, systemd user services, and Windows Scheduled Tasks to a common success/failure contract so non-zero Linux/Windows stop results cannot be mistaken for success. Preserve service definitions and local state whenever stop, verification, definition removal, or remote Worker discovery is incomplete.
29
+ - Make CLI helper timeouts terminate process groups/trees rather than only direct children, including forced escalation for descendants that ignore graceful termination.
30
+
31
+ ### Security, privacy, and local automation
32
+
33
+ - Expand repository privacy and log redaction coverage for npm authentication, common cloud/source-control/chat/payment tokens, JWT-shaped values, embedded-credential URLs, broader private-key headers, credential-shaped filenames, and non-example identities. Scanner read/traversal failures now fail closed without echoing matched values.
34
+ - Add final-component no-follow reads in restricted filesystem paths, reject NUL application action values, and normalize browser-upload filenames and MIME types to prevent deceptive metadata or downstream form parsing ambiguity.
35
+ - Close authenticated browser broker sockets with protocol-specific WebSocket codes for oversized, invalid UTF-8/JSON, or structurally invalid messages instead of silently retaining faulty clients.
36
+
37
+ ### Tests, architecture, and documentation
38
+
39
+ - Replace the hand-maintained JavaScript syntax list with recursive entrypoint discovery; remove stale imports and duplicated lifecycle logic. Add executable concurrency, atomicity, PID-reuse, service-failure, process-tree, state-corruption, unsafe-root, unreadable-job, browser-protocol, upload-metadata, and privacy regression tests.
40
+ - Add `docs/AUDIT.md` and synchronize architecture, security, operations, logging, privacy, managed-job, local-automation, testing, contribution, and release documentation with the implemented fail-closed behavior and residual OS-level limitations.
41
+
3
42
  ## 0.11.1 - 2026-07-12
4
43
 
5
44
  ### Reliable macOS service and orphan-daemon takeover
package/CONTRIBUTING.md CHANGED
@@ -17,7 +17,7 @@ The release operator then creates the matching Git tag and GitHub Release and pu
17
17
  After npm publication, the standard machine update is:
18
18
 
19
19
  ```bash
20
- npm install -g --allow-scripts=esbuild,workerd,sharp machine-bridge-mcp@latest && machine-mcp
20
+ npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest && machine-mcp
21
21
  ```
22
22
 
23
23
  The npm command updates the global CLI. Normal `machine-mcp` startup checks the Worker deployment hash, expected version, and health, redeploys when necessary, and reconciles the daemon/autostart flow.
@@ -34,6 +34,6 @@ Use only synthetic names, reserved example domains, and generic paths. Maintain
34
34
 
35
35
  Read [docs/ENGINEERING.md](docs/ENGINEERING.md) before changing architecture, policy, logging, persistence, transport lifecycle, or release behavior. The default `full` profile is an explicit product invariant and must not be narrowed by an unrelated change.
36
36
 
37
- A log change is behavior: test its level, repetition policy, privacy fields, and recovery message. A transport change must distinguish low-level connectivity from authenticated readiness and test timeout/reconnect branches deterministically.
37
+ A log change is behavior: test its level, repetition policy, privacy fields, and recovery message. A transport change must distinguish low-level connectivity from authenticated readiness and test timeout/reconnect branches deterministically. Lock, state deletion, service lifecycle, detached process, and credential changes require behavior-level concurrency or fault-injection tests. Review [docs/AUDIT.md](docs/AUDIT.md) before changing those surfaces.
38
38
 
39
39
  Reusable decisions belong in tracked documentation. Keep only machine-specific observations in the ignored `.project-local/` directory, and never store credentials there.
package/README.md CHANGED
@@ -79,7 +79,7 @@ On first remote start, the CLI:
79
79
  6. starts an outbound-only daemon connection;
80
80
  7. prints the Remote MCP URL and connection password.
81
81
 
82
- A normal `machine-mcp` invocation is a foreground start: it remains attached to the terminal and prints `info` logs. After a global package upgrade, the CLI unloads the platform service and also checks the workspace lock for a detached/orphan `--daemon-only` process that the service manager no longer tracks. It terminates only a process whose PID, entrypoint, canonical workspace, canonical state root, and daemon-only arguments all match, waits up to 15 seconds for the PID and lock to disappear, and then starts the installed version in the foreground. A genuine foreground or unverifiable conflict is left untouched and reported with actionable guidance. To run only in the background, use `machine-mcp service start`; inspect its owner-only logs under `~/.local/state/machine-bridge-mcp/logs/`.
82
+ A normal `machine-mcp` invocation is a foreground start: it remains attached to the terminal and prints `info` logs. Startup and other state-changing operations use an owner-token/process-identity lock and wait up to 30 seconds for an ordinary concurrent startup to finish instead of failing on a brief launchd/systemd race. After a global package upgrade, the CLI unloads the platform service and also checks the workspace lock for a detached/orphan `--daemon-only` process that the service manager no longer tracks. It terminates only a process whose PID, process start time, entrypoint, canonical workspace, canonical state root, and daemon-only arguments all match, waits up to 15 seconds for the PID and lock to disappear, and then starts the installed version in the foreground. A genuine foreground or unverifiable conflict is left untouched and reported with actionable guidance. To run only in the background, use `machine-mcp service start`; inspect its owner-only logs under `~/.local/state/machine-bridge-mcp/logs/`.
83
83
 
84
84
  Recommended upgrade sequence:
85
85
 
@@ -422,7 +422,7 @@ Default state roots:
422
422
  - Linux with `XDG_STATE_HOME`: `$XDG_STATE_HOME/machine-bridge-mcp`
423
423
  - Windows: `%APPDATA%\machine-bridge-mcp`
424
424
 
425
- State/config writes use owner-only temporary files, flushes, and atomic rename. Malformed state is retained as a bounded corrupt backup before reconstruction. Resource source paths are redacted from `status` output. Active managed-job plans are owner-only and are deleted after a terminal result; bounded redacted results are retained temporarily. Uninstall validates markers, canonical paths, active locks, workspace/source exclusions, and known contents before recursive deletion.
425
+ State/config writes use owner-only temporary files, `fsync`, and atomic replacement. Exclusive locks are fully written before a same-directory hard-link claim becomes visible; lock ownership includes a token and process start time so PID reuse and lock replacement cannot silently transfer ownership. Only successfully read but invalid JSON is retained as a bounded corrupt backup; permission, symbolic-link, size, encoding, and I/O failures remain explicit. A custom state root must not equal, contain, or be contained by the selected workspace. Resource source paths are redacted from `status` output. Active managed-job plans are owner-only and are deleted after a terminal result; bounded redacted results are retained temporarily. Uninstall first acquires a state-root maintenance lock that blocks new profile/state operations and state-backed operations from already constructed managed-job/browser managers, stops the platform service and all verified workspace daemons before removing definitions, rechecks jobs and locks, then validates markers, canonical paths, workspace/source exclusions, and known contents before recursive deletion.
426
426
 
427
427
  Default foreground logs report authenticated relay readiness, readable persistent-degradation summaries, and recovery rather than raw WebSocket callbacks or JSON field dumps. Brief self-healing disconnects and close codes/reasons are debug-only. Stalled connection attempts have a deadline, and sustained-outage reminders use autonomous exponential backoff. Every per-tool event—including success, failure, cancellation, and slow-call timing—also appears only at `--log-level debug` or `--verbose`. Background services use `warn`, so ordinary tool outcomes and brief network changes do not fill daemon logs. Log messages and structured fields are bounded, secret-like keys and known token formats are redacted, and tool arguments/results are not written. See [docs/LOGGING.md](docs/LOGGING.md) and [docs/OPERATIONS.md](docs/OPERATIONS.md).
428
428
 
@@ -437,9 +437,9 @@ npm audit --omit=dev --audit-level=high
437
437
  npm pack --dry-run
438
438
  ```
439
439
 
440
- `npm run check` covers privacy and release-impact gates, architecture/link invariants, generated Worker types, TypeScript, JavaScript syntax, catalog-to-runtime handler parity, deterministic relay lifecycle and secure-file tests, local path/write/process/state/log/service invariants, Ed25519/RSA generation and key-pair validation, real-machine `full` sandbox acceptance, a clean npm package-manifest/sensitive-artifact check, managed-job integrity/redaction/finally/cancellation/recovery, a live stdio MCP flow, and a live local OAuth/Worker/WebSocket/MCP flow. GitHub Actions runs the suite on Linux, macOS, and Windows with the pinned Node 26/npm 12 baseline; macOS and package-audit jobs also exercise the documented isolated global installation.
440
+ `npm run check` covers privacy and release-impact gates, architecture/link invariants, generated Worker types, TypeScript, recursively discovered JavaScript syntax, catalog-to-runtime handler parity, deterministic relay lifecycle and secure-file tests, concurrent lock/atomic-replacement/PID-reuse fixtures, fail-closed service lifecycle tests, descendant process-tree termination, local path/write/state/log/service invariants, Ed25519/RSA generation and key-pair validation, real-machine `full` sandbox acceptance, a clean npm package-manifest/mode/sensitive-artifact check, managed-job integrity/redaction/finally/cancellation/recovery, a live stdio MCP flow, and a live local OAuth/Worker/WebSocket/MCP flow. GitHub Actions runs the suite on Linux, macOS, and Windows with the pinned Node 26/npm 12 baseline; macOS and package-audit jobs also exercise the documented isolated global installation.
441
441
 
442
- See [docs/AGENT_CONTEXT.md](docs/AGENT_CONTEXT.md), [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md), [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md), [docs/TESTING.md](docs/TESTING.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), and [SECURITY.md](SECURITY.md).
442
+ The cross-cutting 0.12.0 review, corrected failure modes, and residual OS-level limits are recorded in [docs/AUDIT.md](docs/AUDIT.md). See also [docs/AGENT_CONTEXT.md](docs/AGENT_CONTEXT.md), [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md), [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md), [docs/TESTING.md](docs/TESTING.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), and [SECURITY.md](SECURITY.md).
443
443
 
444
444
  ## Uninstall
445
445
 
@@ -448,7 +448,7 @@ machine-mcp uninstall
448
448
  npm uninstall -g machine-bridge-mcp
449
449
  ```
450
450
 
451
- Use `--keep-worker` to retain deployed Workers while removing local state and autostart.
451
+ Use `--keep-worker` to retain deployed Workers while removing local state and autostart. Uninstall is fail-closed: if the platform service, a verified daemon, a managed job, an unreadable lock, or a service-definition removal cannot be resolved safely, service definitions and local state are retained for diagnosis instead of being partially deleted.
452
452
 
453
453
  ## License
454
454
 
package/SECURITY.md CHANGED
@@ -95,13 +95,19 @@ A canonical `full` profile guarantees that Machine Bridge itself will not reject
95
95
 
96
96
  ## Mutation integrity
97
97
 
98
- 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.
98
+ Writes are bounded, reject symbolic-link/non-regular destinations, use same-directory staging, flush staged data, and commit atomically per file. Create-only and lock claims fully write a private temporary file before a same-directory hard-link claim becomes visible; a concurrent destination fails without exposing partial content. Replacement writes use `fsync` plus atomic rename/replacement. Expected hashes and exact edits reduce accidental stale overwrites.
99
99
 
100
- 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. On platforms without descriptor-relative path traversal, a malicious process running as the same local account can still race filesystem or parent-directory replacement between validation and commit. These controls do not claim protection against a hostile same-user namespace, nor do they make a multi-directory patch power-loss atomic.
100
+ Startup, daemon, managed-job transition, recovery, and runner claims record ownership tokens and process start time. Reclamation validates process identity and a file snapshot before removal, limiting PID-reuse and stale-owner races. Recent malformed claims are not deleted immediately. Ordinary startup/state changes wait a bounded interval for a concurrent operation instead of turning a brief service-manager race into an operator error.
101
+
102
+ 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. Existing reads and writes use canonical containment checks and final-component no-follow opens where the platform supports `O_NOFOLLOW`. On platforms without portable descriptor-relative `openat` traversal, a malicious process running as the same local account can still race a parent-directory replacement between validation and open/commit. These controls do not claim protection against a hostile same-user namespace, nor do they make a multi-directory patch power-loss atomic.
103
+
104
+ A custom state root is rejected if it overlaps the selected workspace in either direction. Recursive state removal cross-checks the marker, global selection, profile state, daemon locks, active/unreadable locks, known directory shape, package/source/workspace exclusions, service removal, and managed-job state. Any unresolved condition fails closed and retains state.
101
105
 
102
106
  ## Credential exposure
103
107
 
104
- 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.
108
+ 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 flushed and atomically replaced. A read/type/permission/size/symbolic-link failure is not treated as empty or corrupt state; only successfully read invalid JSON is moved to a bounded corrupt backup.
109
+
110
+ Logs recursively redact known credential fields, private-key headers, npm/GitHub/GitLab/Slack/Google/AWS/live-payment/API token forms, JWT-shaped bearer values, embedded-credential URLs, email addresses, user-home paths, and control characters. These patterns are defense in depth; unknown, transformed, split, encrypted, or application-specific secret forms can still pass through an explicitly requested tool result.
105
111
 
106
112
  First-run or explicit reconnect output can intentionally display the MCP connection password. JSON output and standalone secret rotation redact credentials by default; printing requires the explicit reconnect flag. The daemon secret is never printed in full. Avoid shared terminal logs, shell recordings, screenshots, CI output, or support tickets.
107
113
 
@@ -127,7 +133,7 @@ Active plans are owner-only and may temporarily contain argv, non-secret stdin,
127
133
 
128
134
  Exact canonical and registration-time resource path aliases, exact resource bytes interpreted as text, and bounded exact base64/hex forms are redacted from retained output. This cannot detect partial, transformed, encrypted, compressed, or application-specific encodings. It also cannot redact unrelated secrets inherited through the full parent environment. Use `capture_output: "discard"` whenever a process may echo credentials, and never place a secret directly in argv, ordinary env, stdin, temporary-file content, or a JSON plan.
129
135
 
130
- `finally_steps` run after ordinary success, failure, timeout, and cancellation. Cancellation uses an owner-only marker rather than signaling the runner process itself, so the coordinator remains alive to execute cleanup consistently across platforms. A dead runner is detected on the next daemon or local job-CLI start; stale private resource copies are removed and cleanup is retried. This is best effort. Power loss, disk failure, permanent loss of credentials/network access, SIGKILL without later recovery, or security software denying the cleanup executable can prevent cleanup. Finally steps must be idempotent and safe to repeat. Automatic recovery is capped at three attempts so persistent endpoint-security or executable-policy denial cannot create an endless launch loop. Uninstall refuses to remove local state while managed jobs are active; operators must inspect or cancel them first.
136
+ `finally_steps` run after ordinary success, failure, timeout, and cancellation. Cancellation uses an owner-only marker rather than signaling the runner process itself, so the coordinator remains alive to execute cleanup consistently across platforms. Timeout/cancellation target the process group/tree and keep a forced-termination escalation alive even if the direct child exits before a resistant descendant. Runner identity includes process start time; recovery does not trust a reused PID. A dead runner is detected on the next daemon or local job-CLI start; stale private resource copies are removed and cleanup is retried. This is best effort. Power loss, disk failure, permanent loss of credentials/network access, SIGKILL without later recovery, or security software denying the cleanup executable can prevent cleanup. Finally steps must be idempotent and safe to repeat. Automatic recovery is capped at three attempts so persistent endpoint-security or executable-policy denial cannot create an endless launch loop. Uninstall refuses to remove local state while managed jobs are active; operators must inspect or cancel them first.
131
137
 
132
138
  Job-scoped `temporary_files` should be used instead of loose helper scripts. They are materialized only below the private job runtime. Remote scripts should preferably be sent through a process stdin instead of written to the remote filesystem.
133
139
 
@@ -165,7 +171,7 @@ Process sessions are interactive and intentionally die with runtime disconnect/r
165
171
 
166
172
  ## Logs and privacy
167
173
 
168
- Default operational logs record startup/deployment, relay, protocol, service, and infrastructure transitions. Every ordinary per-tool event—start, success, failure, cancellation, and duration—is debug-only. Tool arguments, command text, stdin, file/patch contents, and outputs are omitted. Messages and fields are bounded; unexpected daemon and Worker infrastructure exceptions are reduced to error classes. Git author email is omitted from `git_log` unless explicitly requested.
174
+ Default operational logs record startup/deployment, relay, protocol, service, and infrastructure transitions. Every ordinary per-tool event—start, success, failure, cancellation, and duration—is debug-only. Tool arguments, command text, stdin, file/patch contents, and outputs are omitted. Messages and fields are bounded; unexpected daemon and Worker infrastructure exceptions are reduced to error classes. Static operator guidance uses sanitized plain output; raw plain output is reserved for explicitly requested credentials or local paths. Git author email is omitted from `git_log` unless explicitly requested.
169
175
 
170
176
  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. Managed-job result files may contain remote command output and are owner-only local data, not operational logs.
171
177
 
@@ -188,6 +194,8 @@ Also:
188
194
  - patch the OS and use the repository-pinned Node.js 26/npm 12 baseline;
189
195
  - enable MFA on Cloudflare, GitHub, and npm accounts;
190
196
  - do not configure broad CORS origins;
197
+ - keep the state root completely separate from every workspace; never point `--state-dir` at a project directory or one of its ancestors;
198
+ - treat an unreadable or malformed live lock as an incident to inspect, not a file to delete blindly;
191
199
  - select `agent`, `edit`, or `review` instead of the default `full` when broad authority is unnecessary;
192
200
  - inspect client names and OAuth redirect URIs;
193
201
  - rotate secrets after suspected disclosure;
@@ -197,6 +205,8 @@ Also:
197
205
  - remove the Worker and state when remote access is no longer needed;
198
206
  - use external OS isolation for untrusted code.
199
207
 
208
+ The full cross-cutting review and residual limitations are recorded in [docs/AUDIT.md](docs/AUDIT.md).
209
+
200
210
  ## Out of scope
201
211
 
202
212
  The project cannot prevent an authorized client from requesting data accessible to enabled tools, make arbitrary local executables safe, identify all sensitive content, guarantee cleanup across every power/storage/security failure, override MCP-host or endpoint-security policy, or neutralize model prompt injection. Operator approval, local resource hygiene, idempotent cleanup, and narrow capability selection remain primary controls.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Machine Bridge Browser",
4
- "version": "0.11.1",
4
+ "version": "0.12.1",
5
5
  "description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -29,5 +29,5 @@
29
29
  "action": {
30
30
  "default_title": "Machine Bridge Browser"
31
31
  },
32
- "version_name": "0.11.1"
32
+ "version_name": "0.12.1"
33
33
  }
@@ -189,7 +189,7 @@ No persistent skill or project-context index is trusted as authoritative. `sessi
189
189
 
190
190
  ## Progressive disclosure and task selection
191
191
 
192
- `agent_context` returns bounded skill metadata. `load_local_skill` returns full instructions only for one selected bundle. `resolve_task_capabilities` tokenizes the current task, ranks skill names/descriptions and command names/descriptions/argv, returns matches with scores, and loads the leading skill only when its relevance threshold is met.
192
+ `agent_context` returns bounded skill metadata. `load_local_skill` returns full instructions only for one selected bundle. `resolve_task_capabilities` tokenizes the current task, ranks skill names/descriptions and command names/descriptions/argv, returns matches with scores, and loads the leading skill only when its relevance threshold is met. Hyphens, underscores, dots, and whitespace are normalized for matching, while an explicitly named skill or registered command receives a strong deterministic boost. Thus a task that says “agents progressive disclosure” can match `agents-progressive-disclosure` without relying on generic description words.
193
193
 
194
194
  This ranking is deterministic local assistance, not semantic certainty. The model must still evaluate whether the selected skill applies. Machine Bridge does not execute skill scripts implicitly and does not fabricate a dynamically named MCP tool per skill.
195
195
 
@@ -18,9 +18,9 @@ No transport is treated as a sandbox. Both transports invoke the same local runt
18
18
 
19
19
  ### CLI and state layer
20
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.
21
+ The CLI canonicalizes workspaces, resolves policy profiles, maintains per-workspace state and credentials, serializes startup/deploy/rotation with process-identity locks, deploys the Worker, installs optional platform-native autostart, and starts either remote daemon or stdio mode.
22
22
 
23
- A canonical workspace receives an independent profile, Worker name, secret set, resource registry, managed-job directory, daemon/startup locks, and state file. State schema version 5 records policy origin/revision and local resource metadata in addition to the capability fields.
23
+ A canonical workspace receives an independent profile, Worker name, secret set, resource registry, managed-job directory, daemon/startup locks, and state file. State schema version 5 records policy origin/revision and local resource metadata in addition to the capability fields. `exclusive-file.mjs` owns complete-before-visible exclusive claims and flushed atomic replacement. `process-identity.mjs` owns PID liveness, process start-time comparison, bounded command-line inspection, and PID-reuse classification. `service-lifecycle.mjs` owns the fail-closed stop-daemons-before-remove state machine shared by service removal and full uninstall.
24
24
 
25
25
  ### Local runtime
26
26
 
@@ -40,7 +40,7 @@ A canonical workspace receives an independent profile, Worker name, secret set,
40
40
 
41
41
  `RelayConnection` owns remote WebSocket transport, authenticated `hello_ack` readiness, heartbeat liveness, reconnect backoff, and outage logging. Stdio mode invokes `LocalRuntime` directly without that adapter.
42
42
 
43
- `daemon-process.mjs` owns workspace-daemon inspection and takeover. It distinguishes platform service state from the lock-owning Node process, canonicalizes workspace/state paths before identity comparison, parses bounded process command lines without executing them, and sends `SIGTERM` only to a verified same-workspace `--daemon-only` process. CLI orchestration never treats a missing launchd/systemd job as proof that the process exited.
43
+ `daemon-process.mjs` owns workspace-daemon inspection and takeover. It distinguishes platform service state from the lock-owning Node process, validates PID and process-start identity, canonicalizes workspace/state paths before comparison, parses bounded process command lines without executing them, and sends `SIGTERM` only to a verified same-workspace `--daemon-only` process. CLI orchestration never treats a missing launchd/systemd job as proof that the process exited.
44
44
 
45
45
  ### Agent context and capability resolver
46
46
 
@@ -113,6 +113,8 @@ The daemon attachment deliberately omits workspace path/name/hash and process ID
113
113
 
114
114
  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. launchd/systemd definitions contain the workspace/state-root selectors, a `warn` log-level setting, and a sanitized absolute-only PATH captured at installation so background `full` mode can resolve the same developer tools without accepting relative PATH entries.
115
115
 
116
+ The platform adapters normalize launchd, systemd, and Windows Scheduled Task operations to one `{ok, provider}` result contract. Removal is not a provider-specific sequence: `service-lifecycle.mjs` first stops the provider, then every verified workspace daemon in scope, and only then removes the definition. A failed stop or unverifiable process prevents definition/state deletion.
117
+
116
118
  ## Trust boundaries
117
119
 
118
120
  ```mermaid
@@ -170,7 +172,7 @@ The workspace is canonicalized and compared with targets through consistent plat
170
172
 
171
173
  Path behavior is profile-dependent. The default `full` profile permits unrestricted direct filesystem paths and returns absolute paths. The `agent`, `edit`, and `review` profiles enforce canonical workspace containment and return workspace-relative paths. Error strings redact canonical and common platform-alias forms of workspace, runtime, and home paths whenever absolute path display is disabled. Access scope and path display are independent: unrestricted access with path display disabled returns relative workspace paths and opaque external-path identifiers.
172
174
 
173
- Symbolic-link destinations and non-regular write targets are rejected. Recursive walkers do not follow symbolic-link directories.
175
+ Symbolic-link destinations and non-regular write targets are rejected. Existing bounded reads add final-component `O_NOFOLLOW` where supported. Recursive walkers do not follow symbolic-link directories. Because portable Node.js lacks descriptor-relative `openat` traversal for every operation, parent-directory replacement by hostile same-user code remains an external-isolation concern.
174
176
 
175
177
  ## Mutation model
176
178
 
@@ -200,7 +202,7 @@ The default `full` profile passes the complete parent environment. Isolated envi
200
202
 
201
203
  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.
202
204
 
203
- Child processes run in a separate process group where supported. Timeout, cancellation, disconnect, and replacement send termination to ordinary process trees, with forced escalation for timeout/disconnect paths.
205
+ Child processes run in a separate process group where supported. Timeout, cancellation, disconnect, and replacement send termination to process trees, with a referenced forced-escalation timer that remains alive even when the direct child exits before a resistant descendant. Windows uses tree-aware task termination.
204
206
 
205
207
  Managed jobs use the same argv/environment primitives but a different lifecycle. Each job is capped at 16 main and 16 finally steps, 50 retained jobs, 64 registered resources, 8 MiB of referenced resource bytes, 512 KiB of temporary-file content, and bounded per-step output. They are non-interactive. Resource paths/stdin/environment are injected only inside the runner. Exact resource output redaction is defense in depth; discard capture is the strong option when a command may echo credentials.
206
208
 
@@ -214,11 +216,15 @@ The Worker also treats a new connection as a bounded candidate until it authenti
214
216
 
215
217
  ## Persistence
216
218
 
217
- Local state and global config are owner-only, versioned, size-bounded, and written through temporary files, flushes, and atomic rename. Machine-level browser pairing state is owner-only and shared across workspace runtimes through the local broker; its bearer token is not part of workspace state responses. State, managed-job manager, and detached runner commits share a bounded retrying atomic-replace primitive for transient Windows `EPERM`, `EACCES`, `EBUSY`, and `ENOTEMPTY` sharing failures. Managed-job manager and detached runner use one shared no-follow, size-bounded regular-file reader; transition and recovery locks use one PID-lock primitive with bounded stale-lock reclamation and optional runner handoff. Malformed or oversized state becomes a bounded-count `.corrupt-*` backup. Resource paths are omitted from redacted status output. Custom roots are adopted only when empty or recognizable as legacy Machine Bridge state.
219
+ Local state and global config are owner-only, versioned, and size-bounded. Shared persistence primitives write a complete private temporary file, `fsync` it, and either hard-link it for an exclusive claim or atomically replace the destination. Machine-level browser pairing state is owner-only and shared across workspace runtimes through the local broker; its bearer token is not part of workspace state responses. State, managed-job manager, detached runner, browser pairing, and service definitions share the same flushed atomic-replacement primitive plus bounded retry for transient Windows sharing failures.
220
+
221
+ Process locks contain purpose, workspace, ownership token, lock time, and process start time. Stale removal rechecks device/inode/size/mtime and token so an old observer cannot delete a replacement lock. Recent malformed claims receive a grace period. Startup/state operations wait a bounded interval; daemon and runner identity remain process-lifetime locks. Managed-job transition and recovery locks use the same ownership/snapshot principles and support an atomic runner handoff.
222
+
223
+ Only successfully read but syntactically invalid JSON is moved to a bounded `.corrupt-*` backup. Permission, type, symbolic-link, size, encoding, and I/O failures propagate. A state root must be disjoint from its selected workspace. Resource paths are omitted from redacted status output. Custom roots are adopted only when empty or recognizable as legacy Machine Bridge state.
218
224
 
219
- Active managed jobs persist an owner-only plan, status, runner PID, and bounded runner diagnostics. Terminal jobs delete the full plan and retain only bounded status/redacted results for up to seven days. This balances crash cleanup with minimization of scripts, stdin, argv, environment overrides, and resource source paths.
225
+ Active managed jobs persist an owner-only plan, status, runner process identity, and bounded runner diagnostics. Terminal jobs delete the full plan and retain only bounded status/redacted results for up to seven days. This balances crash cleanup with minimization of scripts, stdin, argv, environment overrides, and resource source paths.
220
226
 
221
- 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.
227
+ Removal first acquires a state-root maintenance lock that blocks new profile/state claims and state-backed operations from already constructed managed-job/browser managers, then stops the platform service and all known verified workspace daemons. It then validates the state marker, canonical target, known contents, active or unreadable locks, filesystem root/home/current/package/workspace/source exclusions, managed jobs, and Worker deletion outcome before recursive deletion. Any unresolved phase retains definitions and state.
222
228
 
223
229
  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.
224
230
 
package/docs/AUDIT.md ADDED
@@ -0,0 +1,110 @@
1
+ # Engineering and security audit
2
+
3
+ This document records the cross-cutting audit performed for version 0.12.0. It complements, but does not replace, the continuously enforced contracts in `SECURITY.md`, `docs/ENGINEERING.md`, and the test suite.
4
+
5
+ ## Scope
6
+
7
+ The review covered:
8
+
9
+ - CLI startup, foreground/background takeover, state locks, service installation/removal, and full uninstall;
10
+ - canonical paths, state-root deletion, atomic persistence, symbolic links, permissions, and process identity;
11
+ - daemon/Worker transport, OAuth, relay replacement, request limits, and failure reporting;
12
+ - direct processes, shell execution, interactive sessions, managed jobs, cancellation, recovery, and cleanup;
13
+ - browser pairing, broker authentication, uploads, DOM actions, local application automation, and sensitive fields;
14
+ - logging, privacy scanning, package contents, release scripts, documentation consistency, CI, and dead code.
15
+
16
+ The audit used source review, state-machine analysis, hostile-input tests, concurrent-process fixtures, process-tree fixtures, package dry runs, dependency audits, and the existing remote/stdio integration suites.
17
+
18
+ ## High-impact findings and corrections
19
+
20
+ ### Lock and process identity
21
+
22
+ Process locks were previously created directly at their final path and then populated. A competitor could observe an empty or partial JSON file. Locks also relied primarily on PID liveness, which cannot distinguish PID reuse.
23
+
24
+ The runtime now:
25
+
26
+ - writes and `fsync`s a private temporary file before atomically claiming a lock with a same-directory hard link;
27
+ - records a random ownership token and process start time;
28
+ - validates PID liveness, process start time, lock age, purpose, workspace, and file identity;
29
+ - removes a stale lock only when its device, inode, size, modification time, and token still match the inspected snapshot;
30
+ - gives a recent malformed lock a grace period rather than deleting a possibly in-progress claim;
31
+ - waits a bounded interval for ordinary startup/state operations instead of failing immediately.
32
+
33
+ Managed-job transition, recovery, and runner identity follow the same principles while retaining compatibility with legacy numeric PID files.
34
+
35
+ ### Service lifecycle
36
+
37
+ Service providers returned inconsistent result shapes, and removal could continue after an incomplete stop. Full uninstall considered too little profile state.
38
+
39
+ The lifecycle is now fail-closed and ordered:
40
+
41
+ 1. stop the platform service;
42
+ 2. stop every verified workspace daemon in scope;
43
+ 3. confirm each stop result;
44
+ 4. remove the service definition only after all stops succeed;
45
+ 5. retain service definitions and state when any phase fails.
46
+
47
+ macOS, systemd, and Windows Scheduled Tasks now return a common `ok` contract. Full uninstall scans all known profiles and can recover workspace identity from daemon locks when a state file is unavailable.
48
+
49
+ ### State persistence and deletion
50
+
51
+ State reads previously classified any read error as corrupt JSON and could continue with an empty state. A dangerously selected state root could also overlap a workspace.
52
+
53
+ The runtime now:
54
+
55
+ - backs up a file only after it was read successfully and its JSON content is actually invalid;
56
+ - propagates permission, type, symbolic-link, size, encoding, and I/O failures;
57
+ - uses one shared `fsync` plus atomic-replacement primitive for state, job, runner, browser-pairing, and service-definition writes;
58
+ - rejects a state root that overlaps the selected workspace in either direction before creating state;
59
+ - acquires a state-root maintenance lock to block new profile/state operations and state-backed operations from already constructed managed-job/browser managers, and cross-checks deletion against global workspace selection, profile state, daemon locks, state markers, and known directory shape;
60
+ - treats unreadable process locks as an uninstall blocker rather than evidence of inactivity.
61
+
62
+ ### Process-tree cleanup
63
+
64
+ CLI helper commands and managed jobs could terminate the direct child while leaving descendants alive. Escalation timers could be cleared or unreferenced before killing a resistant descendant.
65
+
66
+ Timeout and cancellation now target the process group/tree, keep the escalation timer alive, and issue a bounded `SIGKILL`/Windows tree termination after graceful shutdown. Tests use descendants that deliberately ignore `SIGTERM`.
67
+
68
+ ### Privacy and logging
69
+
70
+ The privacy gate did not inspect tracked npm authentication configuration or several common credential formats. Free-form log sanitization had corresponding gaps.
71
+
72
+ Checks and redaction now cover:
73
+
74
+ - generic, encrypted, OpenSSH, RSA, EC, and DSA private-key headers;
75
+ - npm, GitHub, GitLab, Slack, Google API, AWS, live payment, and common API-secret formats;
76
+ - JWT-shaped bearer values and URLs with embedded credentials;
77
+ - non-example email addresses and absolute user-home paths;
78
+ - credential-shaped publication filenames;
79
+ - authentication, identity, environment interpolation, and credential URLs in a tracked `.npmrc`.
80
+
81
+ Findings report only file, line, and rule. The matched value is never printed. A tracked `.npmrc` remains permitted for non-secret repository settings such as `engine-strict=true`.
82
+
83
+ ### Local automation boundaries
84
+
85
+ Browser upload metadata now requires a safe single-component filename and canonical MIME type. Derived local filenames have controls and separators removed. Application action text rejects NUL bytes. Sensitive browser and Accessibility values remain excluded from results.
86
+
87
+ Restricted filesystem reads now add `O_NOFOLLOW` for the final path component in addition to canonical workspace containment.
88
+
89
+ ### Test and release integrity
90
+
91
+ The hand-maintained syntax file list was replaced by a recursive scanner. Package scripts are checked for missing entrypoints, release helper scripts are packaged, npm package modes are validated, and installation guidance is checked across the CLI and documentation. Dedicated tests cover lock concurrency, atomic replacement, PID reuse, malformed locks, service removal ordering, process-tree termination, state read failures, unsafe state roots, browser upload metadata, and expanded privacy patterns.
92
+
93
+ ## Reviewed areas without material code changes
94
+
95
+ The review also rechecked Worker OAuth/PKCE validation, exact redirect/resource binding, token hashing/version revocation, registration and failure limits, Durable Object serialization, daemon WebSocket authentication/replacement, CSP/no-store response headers, static MCP catalog parity, browser loopback Host/Origin/subprotocol validation, and fixed-code JXA/browser execution. Existing controls and integration coverage were retained.
96
+
97
+ ## Residual limits
98
+
99
+ No review can prove the absence of all defects. Important residual boundaries are:
100
+
101
+ - Node.js cannot provide a portable `openat`-style directory capability for every filesystem operation. Canonical containment plus `O_NOFOLLOW` protects the final component, but hostile same-user code can still race parent-directory replacement. Use a dedicated low-privilege account, VM, or container for untrusted repositories.
102
+ - Same-user malicious processes can inspect memory, signal processes, alter browser profiles, or manipulate files the user can access. Machine Bridge is not an OS sandbox.
103
+ - Hard-link atomic lock claims require a local filesystem that supports same-directory hard links. Failure is explicit; the runtime does not fall back to a partial-write-prone lock.
104
+ - Service-manager behavior varies by OS version and local policy. Provider failures remain visible and fail closed, but operator intervention can still be required.
105
+ - Browser pages, application UIs, project instructions, skills, command manifests, and command output are untrusted content. Human/host approval remains necessary for consequential actions.
106
+ - The MCP host may filter tools, omit server instructions, or block calls before they reach Machine Bridge; the server cannot observe or override that boundary.
107
+
108
+ ## Ongoing review rule
109
+
110
+ A future change to locks, state deletion, service lifecycle, detached processes, credentials, browser/app authority, package contents, or public transport must add a behavior-level regression test and update the applicable security/operations documentation. String-presence tests may supplement, but must not replace, executable state-transition tests.
@@ -13,6 +13,9 @@ This document records project-wide decisions that must survive individual fixes,
13
13
  7. **Generic local automation is structured, not arbitrary evaluation.** Browser/application features may expose broad user authority under canonical `full`, but must not accept caller-provided JavaScript, AppleScript, JXA, or extension code.
14
14
  8. **Daily-browser integration uses the existing profile.** The supported primary browser path is the packaged authenticated extension and machine-level loopback broker, preserving current tabs/login state; a separate automation profile is not an equivalent replacement.
15
15
  9. **Pairing and resource secrets are not conversation or log data.** Tokens and injected local-resource values must not be returned, embedded in URLs, or written to operational logs.
16
+ 10. **Exclusive claims are complete before visible.** Never create a final lock/PID claim and then populate it. Use the shared exclusive-file primitive, ownership tokens, process-start identity, and snapshot-checked reclamation.
17
+ 11. **Service and state removal are fail-closed state machines.** Stop the platform provider and every verified daemon before removing definitions or recursive state. An unreadable lock, failed stop, active job, or ambiguous identity retains state for diagnosis.
18
+ 12. **Read failure is not empty state.** Permission, type, symbolic-link, size, encoding, and I/O errors must propagate. Corrupt backup/reconstruction applies only after a successful read proves that JSON content is invalid.
16
19
 
17
20
  A proposed change that conflicts with an invariant requires an explicit owner decision and corresponding documentation update. It must not be hidden inside an unrelated refactor.
18
21
 
@@ -49,7 +52,9 @@ Rules:
49
52
  - State transitions are explicit; readiness is not inferred from a lower-level event. For example, an open WebSocket is not an authenticated relay until `hello_ack` is received.
50
53
  - Every externally controlled input is bounded before expensive allocation, traversal, parsing, storage, or execution.
51
54
  - Repository text must not contain invisible ASCII controls other than tab, CR, and LF; architecture tests enforce this even when JavaScript syntax remains valid.
52
- - Persistent mutations use owner-only files, bounded reads, atomic replacement, and integrity checks appropriate to the data.
55
+ - Persistent mutations use owner-only files, bounded no-follow reads, flushed atomic replacement, and integrity checks appropriate to the data.
56
+ - Exclusive locks use the shared complete-before-visible hard-link claim. Reclamation requires process identity plus a matching file snapshot/token; do not unlink a path merely because an earlier read looked stale.
57
+ - Service providers normalize success/failure to one result contract. Definition removal follows the shared platform-stop → verified-daemon-stop → remove order.
53
58
  - Retry is limited to classified transient failures. Authentication, authorization, validation, integrity, and policy errors fail immediately.
54
59
  - Cleanup-only catches may be best effort, but primary failures must not be silently discarded.
55
60
  - New work should not increase an already broad orchestration module when the behavior has an independent lifecycle or test surface. Extract the domain first.
@@ -102,9 +107,10 @@ A higher branch count is acceptable only when the function is an explicit state
102
107
  - Preserve a stable coarse error class for automation and a concise human message for operators.
103
108
  - Do not retry an operation merely because it failed; retry only when the error is positively classified as transient and the operation is idempotent or server state is checked after ambiguity.
104
109
  - When a remote write may have succeeded before the response was lost, query authoritative state before repeating it.
105
- - Timeouts must terminate the relevant process or connection and clear delayed escalation timers after exit.
110
+ - Timeouts must terminate the relevant process tree. Forced escalation must remain referenced until resistant descendants are handled; do not clear it merely because the direct child exited.
106
111
  - Half-open connections need liveness detection, not only periodic writes.
107
- - Lock reclamation must consider both process liveness and absolute age to defend against PID reuse.
112
+ - Lock reclamation must consider process liveness, process start time, absolute age, ownership token, and file identity to defend against PID reuse and replacement races.
113
+ - State/config recovery may classify only parse/root-shape failures as corrupt content. It must not convert read failures into a new empty state.
108
114
  - A recovery path must be bounded and converge to a terminal state rather than retry forever.
109
115
 
110
116
  ## Testing rules
@@ -119,7 +125,9 @@ The required matrix includes:
119
125
  - Linux, macOS, and Windows on the pinned Node/npm baseline;
120
126
  - privacy and release-impact gates;
121
127
  - package-manifest and sensitive-artifact inspection;
122
- - type and syntax checks;
128
+ - generated type checks and recursively discovered JavaScript/shell syntax checks;
129
+ - concurrent exclusive-lock/atomic-replacement tests, PID-reuse/age tests, and fail-closed service-lifecycle tests;
130
+ - real process-tree timeout/cancellation tests with descendants that ignore graceful termination;
123
131
  - local runtime and real `full` acceptance tests;
124
132
  - stdio JSON-RPC integration;
125
133
  - Worker OAuth/WebSocket/MCP integration;
@@ -156,6 +164,8 @@ Do not store passwords, tokens, private keys, authorization URLs, or copied secr
156
164
 
157
165
  When a local observation becomes generally true, move the sanitized lesson into tracked documentation and delete the stale local note.
158
166
 
167
+ The 0.12.0 cross-cutting audit and residual limits are recorded in [AUDIT.md](AUDIT.md).
168
+
159
169
  ## Review checklist
160
170
 
161
171
  A thorough review asks:
@@ -60,7 +60,7 @@ The broker is machine-global rather than workspace-global. One local owner liste
60
60
  - `browser_inspect_page` returns bounded interactive-element metadata.
61
61
  - `browser_action` performs one structured navigation or DOM action. Navigation accepts absolute `http`, `https`, or `file` URLs; script/data schemes are rejected.
62
62
  - `browser_fill_form` fills up to 200 fields and can submit once.
63
- - `browser_upload_files` sets a file input from up to eight registered local resources.
63
+ - `browser_upload_files` sets a file input from up to eight registered local resources. Caller filenames must be safe single-component names; derived names have controls and separators removed. MIME overrides must be canonical media types.
64
64
  - `browser_screenshot` returns native MCP image content.
65
65
 
66
66
  Selectors can use CSS, ID, field name, label text, visible text, ARIA/implicit role, placeholder, and a zero-based match index. The fixed page module traverses open Shadow DOM roots; closed shadow roots remain inaccessible. For frame-specific work, inspect all frames first and then pass `frame_id` to an action.
@@ -74,7 +74,7 @@ machine-mcp resource add account-password /path/to/owner-only/password.txt
74
74
  machine-mcp resource add application-pdf /path/to/document.pdf
75
75
  ```
76
76
 
77
- Then use `value_resource` for a text field or `resources` for `browser_upload_files`. The local daemon reads the resource only when executing the operation. Results report the alias and outcome, not the resource content. This reduces model-context and result exposure, but the destination page receives the value by design and can transmit it according to its own behavior.
77
+ Then use `value_resource` for a text field or `resources` for `browser_upload_files`. The local daemon reads the resource only when executing the operation. Results report the alias and outcome, not the resource content. Upload metadata is normalized before reaching the page so path separators, controls, deceptive relative names, and malformed MIME values are rejected. This reduces model-context and result exposure, but the destination page receives the value by design and can transmit it according to its own behavior.
78
78
 
79
79
  ## Application tools
80
80
 
@@ -83,7 +83,9 @@ Then use `value_resource` for a text field or `resources` for `browser_upload_fi
83
83
  - `inspect_local_application` returns a bounded macOS Accessibility tree.
84
84
  - `operate_local_application` performs a structured Accessibility action.
85
85
 
86
- macOS UI inspection and actions require Accessibility permission for the Node/Machine Bridge process. Application discovery and opening are available on supported desktop platforms; structured UI inspection currently targets macOS. The application backend uses fixed JXA implementation code. The caller selects only an application, structured selector, action, and optional bounded value. `include_values` never returns values for secure-text roles or controls whose metadata indicates passwords, tokens, one-time codes, or payment-card secrets.
86
+ macOS UI inspection and actions require Accessibility permission for the Node/Machine Bridge process. Application discovery and opening are available on supported desktop platforms; structured UI inspection currently targets macOS. The application backend uses fixed JXA implementation code and requires a non-empty JSON result from `osascript`; missing or malformed helper output fails explicitly. The caller selects only an application, structured selector, action, and optional bounded value; NUL-containing action text is rejected. `include_values` never returns values for secure-text roles or controls whose metadata indicates passwords, tokens, one-time codes, or payment-card secrets.
87
+
88
+ Menu-bar and menu subtrees are not recursively expanded by default. This keeps main-window controls within the element/time budget for applications with large localized menu hierarchies. Pass `include_menus: true` to `inspect_local_application` or `operate_local_application` only when the target is a menu item.
87
89
 
88
90
  ## Capability discovery and automatic selection
89
91
 
package/docs/LOGGING.md CHANGED
@@ -93,9 +93,11 @@ Application discovery and Accessibility operations follow the same rule: permiss
93
93
 
94
94
  ## Bounding and redaction
95
95
 
96
- Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Control characters and Unicode display controls are neutralized. Fields with secret-like names, path-like keys, and known token formats are recursively redacted.
96
+ Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Control characters and Unicode display controls are neutralized. Fields with secret-like names and path-like keys are recursively redacted. Free-form sanitization covers generic private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, JWT-shaped values, URLs with embedded credentials, email addresses, and user-home paths.
97
97
 
98
- This is defense in depth, not content classification. Unknown secret formats can evade pattern matching, which is why tool arguments and outputs are omitted rather than merely filtered.
98
+ The logger exposes two intentional plain-output boundaries. `safePlain` sanitizes operational guidance and diagnostic text. Raw `plain` output is reserved for explicitly requested credentials or local paths whose display is the command's purpose; callers must not pass external exception text or tool content to it.
99
+
100
+ This is defense in depth, not content classification. Unknown, split, transformed, encrypted, or application-specific secret formats can evade pattern matching, which is why tool arguments and outputs are omitted rather than merely filtered.
99
101
 
100
102
  ## Files
101
103
 
@@ -120,6 +122,6 @@ Use `server_info`, `machine-mcp status`, `machine-mcp doctor`, and `diagnose_run
120
122
 
121
123
  ## Adding or changing logs
122
124
 
123
- A default-level message must be actionable, privacy-preserving, and resistant to repetition. Add a regression test for severity and field visibility. Raw protocol values belong at debug unless a user can act on them without external documentation.
125
+ A default-level message must be actionable, privacy-preserving, and resistant to repetition. Add a regression test for severity and field visibility. Raw protocol values belong at debug unless a user can act on them without external documentation. New plain-output calls require explicit review of whether `safePlain` is sufficient.
124
126
 
125
127
  See [ENGINEERING.md](ENGINEERING.md) for the project-wide review rules.
@@ -273,10 +273,14 @@ Never place a secret directly in `argv`, `env`, `stdin`, a temporary file's `con
273
273
 
274
274
  ## Persistence and privacy
275
275
 
276
- Per-workspace jobs are stored below the owner-only profile directory. Active jobs retain an owner-only plan for crash recovery. After a terminal status is committed, the full plan is deleted, including argv, stdin, embedded temporary-file content, and resource source paths.
276
+ Per-workspace jobs are stored below the owner-only profile directory. Active jobs retain an owner-only plan for crash recovery. Plan, status, result, runner identity, and lock updates use flushed atomic replacement or complete-before-visible exclusive claims. Transition/recovery locks contain ownership tokens and process start time and are removed only when their file snapshot still matches. After a terminal status is committed, the full plan is deleted, including argv, stdin, embedded temporary-file content, and resource source paths.
277
277
 
278
278
  Retained public job data contains bounded status and redacted results. Up to 50 jobs are retained for up to seven days. Private runtime copies are removed after the finally phase. Runner stdout/stderr log files contain only runner-level diagnostics; step output is not written to those operational logs.
279
279
 
280
+ The detached runner records both PID and process start time. Recovery therefore rejects a reused PID instead of treating an unrelated process as the active runner; legacy numeric PID files remain readable during migration. Recovery-lock handoff preserves a random ownership token, and the runner removes only a lock whose PID, token, and file snapshot still match. Timeout and cancellation terminate the process group/tree, retain a referenced forced-escalation timer, and clean descendants that ignore graceful termination before the runner exits.
281
+
282
+ Missing job JSON is distinct from unreadable or invalid job JSON. Permission, type, symbolic-link, size, UTF-8, I/O, and parse failures retain the job directory, produce an `unreadable` status for listing, skip automatic recovery/pruning, and block uninstall until an operator inspects the state.
283
+
280
284
  Process sessions and managed jobs have different semantics:
281
285
 
282
286
  - process sessions are interactive, memory-only, and die with the daemon connection;
@@ -61,9 +61,9 @@ Application UI inspection/actions require Accessibility permission for the Node/
61
61
 
62
62
  `machine-mcp` is a foreground command. It remains attached to the terminal, defaults to `info` logging, and stops on `Ctrl+C`. `machine-mcp service start` launches the installed platform service in the background and returns to the shell; that service uses `warn` logging.
63
63
 
64
- A global npm install changes the CLI files on disk but does not replace an already running Node process. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd/systemd/task scheduling no longer tracks, including legacy locks without mode/version metadata. Before sending `SIGTERM`, Machine Bridge verifies the live command line against the lock, entrypoint, canonical workspace, canonical state root, and daemon-only flag. It waits up to 15 seconds for both the PID and lock to disappear and never escalates to a forced kill. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
64
+ A global npm install changes the CLI files on disk but does not replace an already running Node process. Startup and other state-changing CLI operations use a token/process-identity lock and wait up to 30 seconds for a normal concurrent startup to finish; a short launchd/systemd overlap is therefore serialized rather than reported immediately as an error. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd/systemd/task scheduling no longer tracks, including legacy locks without mode/version metadata. Before sending `SIGTERM`, Machine Bridge verifies PID and process start time plus the live command line, entrypoint, canonical workspace, canonical state root, and daemon-only flag. It waits up to 15 seconds for both the PID and lock to disappear and never escalates a daemon takeover to a forced kill. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
65
65
 
66
- `machine-mcp service status [WORKSPACE]` reports two independent layers: the platform service (`active`) and `workspace_daemon`, plus `effective_active` and `orphaned_workspace_daemon` summary flags. On macOS it is possible for launchd to report inactive while a prior Node process remains alive with parent PID 1; that is an orphan-daemon condition, not proof that the daemon stopped. `service stop` now unloads the provider when present and then terminates only a verified service-style workspace daemon. If takeover reaches its deadline, run:
66
+ `machine-mcp service status [WORKSPACE]` reports two independent layers: the platform service (`active`) and `workspace_daemon`, plus `effective_active` and `orphaned_workspace_daemon` summary flags. On macOS it is possible for launchd to report inactive while a prior Node process remains alive with parent PID 1; that is an orphan-daemon condition, not proof that the daemon stopped. `service stop` unloads the provider when present and then terminates only a verified service-style workspace daemon. `service uninstall` and full uninstall are ordered fail-closed operations: provider stop → verified daemon stop(s) → definition removal. A failed or ambiguous stop leaves definitions and state intact. If takeover reaches its deadline, run:
67
67
 
68
68
  ```sh
69
69
  machine-mcp service stop
@@ -80,6 +80,12 @@ machine-mcp --verbose
80
80
 
81
81
  Keep `--omit=optional` in the install command. Without it npm may resolve optional `fsevents` and warn that its install script was not included in `allowScripts`; Machine Bridge does not require that development-time watcher at runtime.
82
82
 
83
+ ## State-root safety and removal
84
+
85
+ The state root must be a dedicated directory and must not equal, contain, or be contained by the selected workspace. Do not point `--state-dir` at a project directory. State/config and lock files are owner-only, bounded, and committed through flushed atomic primitives. A permission, type, symbolic-link, size, encoding, or I/O failure is reported; only successfully read invalid JSON is moved to a bounded `.corrupt-*` backup.
86
+
87
+ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state operations and state-backed operations from already constructed managed-job/browser managers, then scans all known profiles, active managed jobs, daemon/startup locks, global workspace selection, profile state, daemon lock workspace metadata, the state marker, and directory shape. It rechecks jobs and locks after stopping services/daemons. An unreadable lock is treated as a blocker, not as inactivity. Do not manually delete a lock merely because it looks old; inspect the recorded PID and command first.
88
+
83
89
  ## Logs
84
90
 
85
91
  Remote autostart definitions prefer a stable PATH alias that resolves to the currently running Node executable and persist a sanitized absolute-only service `PATH` containing the Node/CLI directories, the installer's absolute PATH entries, and platform defaults. This avoids versioned Homebrew-style paths becoming invalid after upgrades and prevents launchd/systemd from falling back to a minimal system-only PATH. Re-run `machine-mcp service install` after changing Node installation families or PATH layout. A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. 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. On a logging-schema upgrade, bounded prior content is moved into `daemon.out.legacy.log` and `daemon.err.legacy.log`; use the active filenames when diagnosing current behavior.
@@ -95,7 +101,7 @@ debug all per-tool starts/successes/failures/cancellations/timing, correlation
95
101
 
96
102
  Foreground mode defaults to `info`; autostart uses `warn`. Use `--verbose` or `--log-level debug` only for diagnosis. `--quiet` is an alias for `--log-level error`.
97
103
 
98
- Normal logs intentionally omit tool arguments, file/patch/image content, command text and argv, stdin/stdout/stderr, OAuth request bodies, connection credentials, authorization codes, and tokens. Unexpected daemon and Worker failures use coarse error classes rather than raw exception messages. Messages and structured fields are bounded and secret-like fields/token formats are redacted.
104
+ Normal logs intentionally omit tool arguments, file/patch/image content, command text and argv, stdin/stdout/stderr, OAuth request bodies, connection credentials, authorization codes, and tokens. Unexpected daemon and Worker failures use coarse error classes rather than raw exception messages. Messages and structured fields are bounded and redact private-key headers, common access-token/API-key/JWT forms, embedded-credential URLs, email addresses, and user-home paths. Raw plain terminal output is reserved for explicitly requested credentials or local paths; operational guidance uses sanitized plain output.
99
105
 
100
106
  See [LOGGING.md](LOGGING.md) for the event contract and MCP-host boundary. Cloudflare observability is sampled and is not a complete audit log.
101
107
 
@@ -215,6 +221,8 @@ After suspected credential or client compromise:
215
221
  1. stop foreground and autostart daemons;
216
222
  2. run `machine-mcp rotate-secrets`;
217
223
  3. restart without broad flags and redeploy;
218
- 4. inspect Cloudflare account access, Worker configuration, local state/resource permissions, managed-job results, and service logs;
224
+ 4. inspect Cloudflare account access, Worker configuration, local state/resource permissions, process-lock owners, managed-job results, and service logs;
219
225
  5. cancel active managed jobs and remove compromised resource aliases;
220
226
  6. remove the Worker and local state if continued remote access is unnecessary.
227
+
228
+ The detailed 0.12.0 audit record and residual operational limits are in [AUDIT.md](AUDIT.md).