machine-bridge-mcp 3.0.0-beta.155 → 3.0.0-beta.158
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 +18 -0
- package/README.md +2 -0
- package/SECURITY.md +7 -0
- package/bin/machine-mcp.mjs +2 -1
- package/browser-extension/manifest.json +1 -1
- package/docs/ARCHITECTURE.md +4 -4
- package/docs/AUDIT.md +40 -0
- package/docs/LOGGING.md +2 -0
- package/docs/OPERATIONS.md +6 -2
- package/docs/TESTING.md +9 -3
- package/docs/THREAT_MODEL.md +1 -1
- package/package.json +1 -1
- package/scripts/check-runner.mjs +2 -2
- package/scripts/coverage-check.mjs +4 -1
- package/scripts/prepare-pinned-npm.mjs +1 -1
- package/scripts/verification-environment.mjs +16 -0
- package/src/local/daemon-http-relay-request.mjs +2 -2
- package/src/local/network-proxy.mjs +24 -2
- package/src/local/relay-connection-classification.mjs +1 -1
- package/src/local/resource-lease-liveness.mjs +5 -2
- package/src/local/resource-staging-recovery.mjs +4 -2
- package/src/local/resource-waiters.mjs +3 -1
- package/src/local/service-environment.mjs +1 -0
- package/src/local/shell.mjs +1 -1
- package/src/worker/index.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.158 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
- Fix a resource-coordinator self-identity race exposed by the exact-main beta.157 source-release gate. Waiter/lease stale-pruning and staging-publisher recovery now treat the running coordinator's own `process.pid` as authoritative through `currentProcessStartTimeMs()` instead of allowing an asynchronous all-process snapshot to override that identity. A generation-skewed external snapshot can therefore no longer delete the acquire call's own waiter and turn an otherwise admitted Green-state request into `fairness_wait`, reclaim a provisional lease owned by the still-running coordinator, or delete a still-live atomic waiter/lease staging file. Foreign PIDs continue to use sampled process-start evidence, so proven PID reuse remains reclaimable and fail-closed liveness semantics are unchanged.
|
|
6
|
+
- Add focused regressions that deliberately supply a mismatched external start generation for the current PID and require the current waiter, provisional lease, and live atomic staging publisher to survive, while a mismatched foreign PID and a same-PID staging artifact from an older process generation are still reclaimed. Direct A/B probes against the accepted beta.157 tree reproduce both failures: the skewed self waiter becomes Green-state `fairness_wait`, and a future-generation snapshot silently deletes a live staging file; the beta.158 implementation admits/preserves both. The beta.157 source-release attempt had passed the first 122/131 frozen tasks before `full-access:test` failed on its first direct process; the wrapper stopped before tag/GitHub/npm mutation, so no published beta.157 artifact is being replaced. A separate cancellation/fairness fixture now injects its known current-process generation so its short observer window measures waiter wakeup semantics rather than a real all-process scan. beta.158 supersedes that accepted but unpublished candidate, and hosted MCP tool schema generation remains 21.
|
|
7
|
+
|
|
8
|
+
## 3.0.0-beta.157 - 2026-08-31
|
|
9
|
+
|
|
10
|
+
- Make release verification hermetic with respect to owner runtime configuration. Fast/full verification and the standalone critical-coverage runner now share one case-insensitive child-environment boundary that removes `MBM_RELAY_PROXY`, `MBM_DEBUG`, `MBM_MACOS_BACKGROUND_VISUAL_BACKEND`, and `MBM_MACOS_TRUST_BROKER`; dedicated fixtures opt into those behaviors explicitly. Ordinary `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`, execution-surface provenance, explicit verification controls such as `MBM_CHECK_CONCURRENCY`, and unrelated environment state remain available. This closes both the second-order case where `coverage:test` could reintroduce a service-only relay route and the broader class where an owner's optional runtime configuration silently changes generic release evidence.
|
|
11
|
+
- Remove host-scheduler timing from release-critical assertions. Resource process-parent coverage now verifies the exact bounded platform probe and conservative `null` fallback through injection instead of requiring the developer/CI machine's real process table to answer inside the production deadline; the elastic admission retry/re-expansion fixture also injects the process-start snapshot it needs instead of spending its one-second synthetic retry budget on a real system-wide process-generation scan under coverage. Remote process-read cooldown/output/exit behavior is exercised with synthetic session state and explicit waiter notifications; the remaining real-child clamp fixtures clear inherited `NODE_V8_COVERAGE` only from their spawned `node -e` children, so parent production modules remain covered without turning child instrumentation overhead into timing evidence. They retain only the hosted one-second wait-clamp lower bound and no longer treat sub-second output/exit timers or scheduler-dependent upper bounds as correctness evidence. The full-only stdio integration likewise separates its fast five-second protocol-response budget from a 20-second real-process observer budget, so `run_process`/`exec_command`/`start_process` can consume their documented pre-spawn admission window plus execution/settlement time without the test harness timing out first; production process deadlines are unchanged. The managed-job resource-replacement recovery fixture keeps its private coordinator and production recovery semantics but clears nested V8 coverage from that dedicated runner, preventing instrumentation latency in an unrelated all-process generation probe from consuming the fixture's ready window; the parent managed-job suite and the rest of the runner coverage remain instrumented.
|
|
12
|
+
- Tighten release/privacy and execution-policy hygiene found during an independent architecture, module, branch, boundary, logging, test, documentation, history, and supply-chain review. The hardened-npm CI bootstrap no longer prints its temporary absolute bin path, and top-level `MBM_DEBUG=1` CLI exceptions now pass stack text through the shared log-redaction boundary so home paths and credential-shaped values are not emitted. Current threat/testing documentation no longer labels live contracts as historical Beta.61/Beta.58 behavior, and the undocumented initial-release `MBM_PASS_ENV=true` escape hatch is removed so a narrow/minimal execution environment can be widened only by the effective policy or accepted managed-job plan—not by ambient daemon/shell state. Behavioral and architecture regressions prevent these privacy/authority bypasses from returning. Reachable-history privacy scanning, full/production npm audit, registry signatures/attestations, architecture, lint, type checking, targeted regressions, and critical coverage remain clean. Several orchestration modules are intentionally left behaviorally unchanged despite low remaining line-budget headroom; future functional work should extract a coherent lifecycle/policy domain before raising those caps or adding more responsibilities.
|
|
13
|
+
- Advance package/runtime identity to beta.157 because verification/CI scripts and the execution-environment hardening are packaged bytes. Hosted MCP tool schema generation remains 21: no tool input/result shape or owner-visible diagnostic schema changes, while narrow-profile environment semantics are tightened to match the already documented policy contract. The already published/activated beta.156 artifact and its soak evidence remain immutable; beta.157 requires its own frozen verification and candidate evidence before any activation/publication, and activating a published beta.157 would restart the major-version soak clock.
|
|
14
|
+
|
|
15
|
+
## 3.0.0-beta.156 - 2026-08-31
|
|
16
|
+
|
|
17
|
+
- Add a relay-only `MBM_RELAY_PROXY` application egress override for deployments where relay continuity must not follow ordinary `NO_PROXY` or shared environment-proxy selection. A non-empty value fixes both the preferred WebSocket and signed HTTP fallback to the same HTTP(S) CONNECT proxy; proxy failure keeps normal relay recovery but never authorizes a direct retry. Empty/unset values preserve the existing `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` behavior, and Worker deployment health remains on that standard model.
|
|
18
|
+
- Persist `MBM_RELAY_PROXY` in the existing owner-only service network-environment snapshot so launchd/systemd/Windows logon startup does not lose a session-only dedicated relay route. Diagnostics continue to expose only the coarse `application-http-proxy` route and never reveal the dedicated endpoint or credentials. Documentation makes the trust boundary explicit: Machine Bridge does not bind the proxy's upstream socket or bypass an operating-system VPN/TUN by itself; true route isolation uses a loopback-only sidecar whose upstream socket and DNS path are independently routed outside the unstable tunnel.
|
|
19
|
+
- Add regressions for dedicated WSS routing despite matching `NO_PROXY`, real signed HTTP-fallback traversal through the same CONNECT proxy, invalid dedicated proxy schemes, and service-environment persistence. This packaged reliability fix blocks beta.155 soak promotion, advances package/runtime identity to beta.156, and leaves hosted tool schema generation at 21 because no MCP tool or owner-visible diagnostic contract changes.
|
|
20
|
+
|
|
3
21
|
## 3.0.0-beta.155 - 2026-08-30
|
|
4
22
|
|
|
5
23
|
- Close the hosted managed-job cross-conversation discovery boundary found during an independent beta.154 review. Hosted acceptance now returns deterministic HMAC-SHA256 `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and purpose. Hosted `read_job` requires the read capability, `cancel_job` requires the control capability, and every `depends_on` reference requires an exact `dependency_recovery` mapping. The Worker verifies these capabilities before daemon dispatch and strips them from daemon arguments; they are not persisted in managed-job state or mirrored into MCP parameter headers. Hosted `list_jobs` is now aggregate-only and omits job IDs, names, and `recent_process_recovery` handles, while local CLI/stdio retains global owner administration. This prevents Machine Bridge's own shared owner inventory from becoming cross-conversation recovery authority; it cannot distinguish conversations if a host itself forwards another conversation's valid capability.
|
package/README.md
CHANGED
|
@@ -129,6 +129,8 @@ https://<worker>.<account>.workers.dev/mcp
|
|
|
129
129
|
|
|
130
130
|
Remote readiness is end-to-end. A daemon becomes available only after a Worker probe traverses the same authenticated local dispatch and result-delivery path used by real tool calls. A replacement daemon is verified before it displaces a healthy incumbent.
|
|
131
131
|
|
|
132
|
+
When the relay must use a proxy but should not inherit an operating-system VPN/TUN path, set `MBM_RELAY_PROXY` to a dedicated HTTP(S) proxy endpoint. It takes precedence over `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` for both the preferred WebSocket relay and signed HTTP fallback; a configured proxy failure never falls back to a direct relay connection. A common deployment is a loopback-only sidecar whose own upstream socket is pinned to the intended physical/network interface. Machine Bridge does not itself bind the sidecar's upstream socket, so pointing `MBM_RELAY_PROXY` at a remote proxy does not by itself bypass an operating-system tunnel. See [docs/OPERATIONS.md](docs/OPERATIONS.md).
|
|
133
|
+
|
|
132
134
|
For account roles, OAuth lifecycle, supported callback behavior, and tenancy limits, read [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) and [docs/MULTI_ACCOUNT.md](docs/MULTI_ACCOUNT.md).
|
|
133
135
|
|
|
134
136
|
## Local stdio quick start
|
package/SECURITY.md
CHANGED
|
@@ -87,6 +87,7 @@ The default `full` profile prioritizes owner automation over least privilege.
|
|
|
87
87
|
- `review` exposes read-only workspace, Git, and image tools.
|
|
88
88
|
|
|
89
89
|
A stored `full` label is canonical. Per-capability narrowing is represented as `custom`, not as a partially restricted `full`.
|
|
90
|
+
Whether a process receives the complete parent environment is derived from that effective policy (or an already accepted managed-job plan). Ambient process variables cannot promote an isolated execution environment to full inheritance. Optional owner runtime settings such as the macOS trust broker and experimental native visual backend are separate configuration inputs, not execution-authority overrides.
|
|
90
91
|
|
|
91
92
|
`run_process` avoids shell parsing but an invoked interpreter, package manager, compiler, test runner, or repository script can execute arbitrary code. `exec_command` additionally enables shell expansion.
|
|
92
93
|
|
|
@@ -190,6 +191,12 @@ Computer Use screenshots share the ordinary MCP result-size boundary. When an im
|
|
|
190
191
|
|
|
191
192
|
Local resources may be injected without returning their bytes through MCP, but the destination page or application still receives them. Screenshots and page source can themselves contain secrets. In particular, raw serialized HTML may contain hidden bootstrap/session/account/authentication values that are not visible in the rendered page; `browser_get_source` therefore should be used only when raw markup is required, with semantic inspection preferred for routine browser work.
|
|
192
193
|
|
|
194
|
+
## Network egress boundaries
|
|
195
|
+
|
|
196
|
+
Machine Bridge can select an application-layer HTTP(S) proxy for relay traffic. A non-empty `MBM_RELAY_PROXY` takes precedence over standard proxy/`NO_PROXY` resolution for both the WebSocket relay and signed HTTP fallback, and a failed proxy connection is not retried directly. Proxy URLs and credentials are not returned through runtime diagnostics or operational logs.
|
|
197
|
+
|
|
198
|
+
This is not an operating-system network-isolation boundary. Machine Bridge does not bind the proxy's upstream socket to a physical interface, implement an independent routing table, or prevent a system VPN/TUN from intercepting traffic to a remotely addressed proxy. When relay traffic must be isolated from such a tunnel, use a loopback-only sidecar whose own outbound socket and DNS path are explicitly routed outside that tunnel. The sidecar and upstream proxy remain separate trusted network components.
|
|
199
|
+
|
|
193
200
|
## Filesystem and mutation integrity
|
|
194
201
|
|
|
195
202
|
Workspace-confined profiles canonicalize existing paths and write ancestors. Final symbolic-link writes are rejected. Patch add, update, delete, and move destinations are classified before mutation.
|
package/bin/machine-mcp.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { main } from "../src/local/cli.mjs";
|
|
3
|
+
import { sanitizeLogText } from "../src/local/log.mjs";
|
|
3
4
|
|
|
4
5
|
main().catch(error => {
|
|
5
6
|
const message = process.env.MBM_DEBUG === "1"
|
|
6
|
-
? (error?.stack || error?.message || String(error))
|
|
7
|
+
? sanitizeLogText(error?.stack || error?.message || String(error))
|
|
7
8
|
: (error?.message || String(error));
|
|
8
9
|
console.error(`Error: ${message}`);
|
|
9
10
|
process.exitCode = 1;
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "3.0.0-beta.
|
|
33
|
+
"version_name": "3.0.0-beta.158",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -254,13 +254,13 @@ Resource command classification distinguishes local resource cost from semantic
|
|
|
254
254
|
|
|
255
255
|
Fixed implementation-owned Git operations are not routed through that arbitrary-process capability. `GitService` owns the filesystem-first repository discovery used by the structured Git tools and `project_overview`: metadata pointers, object stores, alternates, and reference metadata are checked against request-effective path visibility before a Git subprocess starts, and restricted principals additionally reject symbolic-link or special-file descendants in the metadata trees. Structured Git uses code-constructed argv, literal pathspecs, no shell, a minimal isolated environment, bounded output/deadlines, and the same cancellation/process-tree accounting. Repository configuration that redirects local files, executes conversion helpers, or enables lazy object fetching fails closed rather than expanding the structured surface. `git_commit` also refuses merge, rebase, cherry-pick, revert, bisect, and sequencer state before staged-tree plumbing begins, so the narrow single-parent commit path cannot flatten an in-progress history operation; its fixed Git process environment comes from the same exact-value contract that validates internal overrides. The caller cannot choose the executable or inject a command string. Role filtering still applies to returned data and mutation availability; hostile same-UID replacement after inspection remains outside the filesystem-authority guarantee.
|
|
256
256
|
|
|
257
|
-
The default `full` profile passes the complete parent environment. Isolated environment mode, used by the narrower named profiles unless overridden, 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.
|
|
257
|
+
The default `full` profile passes the complete parent environment. Isolated environment mode, used by the narrower named profiles unless overridden by the effective policy/accepted execution plan, creates private runtime HOME, temporary, and cache directories and passes only a small set of path/locale/platform variables. No ambient process variable may promote isolated mode to full parent-environment inheritance. It reduces accidental credential inheritance but cannot prevent explicit access to known filesystem paths, credential stores, network services, or other user resources.
|
|
258
258
|
|
|
259
259
|
`execution-limits.mjs` is the shared source for local tool-call concurrency, one-shot process timeout/stdin/output limits, and process-session count/stdin/output/retention limits. Remote-owner `diagnose_runtime.runtime.execution_guardrails` reports those enforced limits, while local stdio `server_info.runtime.execution_guardrails` exposes the same contract together with explicit `not-enforced` values for CPU quota, memory quota, and network isolation. Public one-shot commands inline at most 32 KiB per stream. When either stream exceeds that preview, the runtime keeps up to 1 MiB per stream in a closed in-memory process session for thirty minutes and returns an `output_session_id`; `read_process` then reads monotonic byte-offset pages. Relay-origin process reads use paced same-response follow-up. `process-session-read.mjs` owns read/wait/output orchestration while `process-session-remote-poll.mjs` owns only hosted projection/cooldown policy, leaving `process-sessions.mjs` focused on session lifecycle and ownership. The async read helper returns its internal plan/output snapshot before hosted projection is finalized; after the await the manager re-checks cancellation, then completes projection synchronously, so cancellation at the Promise boundary is not lost and a child exit there cannot produce `running=false` with stale cooldown metadata. The Worker and daemon cap the actual output/exit blocking wait at one second; a live session that has just consumed a remote blocking read enters a fifteen-second would-block cooldown. A repeated would-block request inside that cooldown is paced inside the same MCP call until output/exit or the cooldown boundary instead of returning an immediate running checkpoint, and the Worker execution budget covers that server-side delay. When `wait_for_exit=true`, ordinary output notifications remain readable but do not release the cooldown stage; only process exit or the monotonic cooldown deadline advances the call to its bounded one-second exit-wait stage. Every live relay-origin read identifies `status_polling_mode=paced_followup` and keeps `host_turn_handoff_recommended=false`; terminal reads identify `status_polling_mode=terminal`. The cooldown fields are explicitly blocking-only (`blocking_poll_throttled`, `next_blocking_poll_after_ms`) so a zero-wait status read is not misrepresented as globally rate-limited. Same-response follow-up is allowed when the task needs additional output or terminal state, but callers must not busy-loop and should respect the reported cooldown. Elapsed wall-clock time is not a host-deadline signal: callers continue bounded follow-up while calls are accepted and hand progress back only after an actual host/tool boundary is observed, external input/authorization is required, or the user explicitly requested a checkpoint. This state is per in-memory process session, does not alter local stdio/CLI wait behavior, and is intentionally a hosted pacing bound rather than durable scheduling. The oldest exited session is evicted before an active session is refused, so continuation retention is explicitly best effort rather than durable. The continuation stores command basename and cwd metadata but not argv or shell text. It is memory-only and disappears on runtime stop or daemon replacement.
|
|
260
260
|
|
|
261
261
|
Resource admission is a separate cooperative boundary, not an OS quota. `resource-foreground-wait.mjs` owns the ordinary process-start wait budget: a one-shot foreground call defaults to 20% of its execution timeout with a two-second floor, ten-second ceiling, and never more than the complete execution timeout. Owner-local process-session startup defaults to a ten-second cooperative wait, while relay-origin `start_process` performs one admission attempt without queueing by default so known host pressure is returned before the request-owned response budget is consumed. An explicit configured override remains authoritative for controlled diagnostics/tests. `LocalRuntime` leaves this default unconfigured in production so the services actually apply it, while explicit overrides are validated and capped at thirty minutes. These admission waits occur before process spawn and do not enlarge an outer relay deadline; cancellation propagates through the same coordinator wait. Detached managed-job steps use the shared durable-delivery admission ceiling instead: the runner may wait up to thirty minutes before spawn, child execution timeout starts only after admission succeeds, and persisted job status reports `current_phase=resource_admission` during that pre-spawn interval. Hosted managed-job status supports bounded autonomous follow-up: `managed-job-hosted-status.mjs` owns the relay-only projection, so relay-origin `read_job` reports `status_polling_mode=bounded_followup` plus `host_turn_handoff_recommended=false` for active jobs while terminal reads report `status_polling_mode=terminal`; local job reads retain their prior shape. Worker tool guidance allows a known active job to be followed again in the same assistant response when terminal state is required, while prohibiting busy loops, repeated `list_jobs` substitution, and speculative handoff based only on elapsed wall-clock time. `managed-job-read-wait.mjs` keeps that autonomy out of the host spin loop: the runtime `read_job` handler performs a relay-only 40-second default long-poll with five-second lightweight status-only probes, monotonic deadline accounting, and cancellation checks. Terminal settlement returns on the next bounded probe. Nonterminal status/phase/dependency progress is coalesced until at least the 30-second hosted progress floor (or the caller's explicitly shorter wait), and `current_step` alone is excluded from the hosted wake signature; local explicit waits retain step-progress wake behavior. The public hosted maximum is 60 seconds; the 40-second default remains intentionally separate so ordinary continuation leaves additional host/Worker settlement headroom, while longer jobs continue through another server-paced read of the same capability-bound job rather than one overlong host request. Full runner/recovery reconciliation is bounded to thirty-second intervals inside the advertised wait rather than executing on every lightweight probe. The monotonic wait deadline starts before the initial full read, so initial reconciliation consumes the same per-call budget, and an unchanged timeout returns the latest secure persisted progress state without adding a second heavyweight reconcile after the deadline. Coherent non-interactive command sequences should use a repository umbrella command or a multi-step managed job so local command fan-out does not become host-visible tool-event fan-out; this density control does not shorten the managed task or its six-hour step ceiling. Hosted reads first verify a healthy runner with async process-start identity sampling; `managed-job-hosted-reconcile.mjs` owns that relay-only liveness/recovery decision, and only a non-current/ambiguous runner enters the existing recovery reconciliation path, so a normal long-running job no longer launches synchronous `ps` from the relay event loop every reconcile interval. `ManagedJobManager.read()` remains the local synchronous persistence/ownership projection while relay reads use `readHosted()`; `readProgress()` deliberately omits runner reconciliation and terminal-result projection during unchanged waits. Relay-origin `list_jobs` remains an `inventory` surface and does not recommend handoff, but its hosted projection is aggregate-only: it omits job IDs, names, and `recent_process_recovery` handles. Hosted acceptance returns deterministic HMAC-derived `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and capability purpose. The Worker validates these credentials before daemon dispatch, strips them before constructing the daemon request, requires `recovery_key` for `read_job` and every `depends_on` reference, and requires `control_key` for `cancel_job`. The capabilities are derived from the existing Worker OAuth identity key and are never persisted with local job state; rotating that identity key invalidates outstanding hosted capabilities. Local CLI/stdio administration intentionally retains global owner visibility. This boundary prevents Machine Bridge's own hosted global inventory from causing cross-conversation job discovery, but it cannot distinguish two host conversations if the host itself incorrectly supplies the other conversation's still-valid capability. Ordinary managed-job result/status projection remains separate from this hosted pacing policy. Owner diagnostics separately expose the Boolean `waiters.drain_active`, computed by the same fairness selection state machine, so Green host/resource pressure is not confused with an aged protected waiter intentionally reserving a drain window. A fixed CPU request that exceeds the machine's best-case priority-specific launch window is structurally incapable of becoming admissible while the configured CPU headroom remains in force; it therefore returns non-retryable `cpu_request_exceeds_launch_window` before entering the retry sleep loop instead of masquerading as transient `cpu_pressure_window`. Explicit worker counts/argv are never rewritten and the headroom is not relaxed; elastic/unbounded requests retain their pressure-aware fitting behavior. `resource-command-profile.mjs` classifies known light operations, bounded adaptive unknowns, and known CPU/I/O/mixed build families. `resource-script-classification.mjs` classifies only an actually executed shell/Node/Python script operand or package-manager script name, and `resource-shell-analysis.mjs` owns conservative shell token/segment parsing. This lets direct orchestration roots reserve startup capacity before descendant fan-out without letting an unrelated argument such as a test filename or release note impersonate a heavy script. The arbitrary-process zero-resource allowlist is intentionally identity-bound: `resource-light-command.mjs` accepts only a small set of standard absolute executables for constant/output, process-table, uptime, and sleep probes. Bare PATH-resolved names and every arbitrary shell invocation remain adaptive even when their apparent command is cheap, because executable resolution, startup configuration, repository configuration, path behavior, options, helpers, or input size can change the actual work. Caller-controlled Git, filesystem metadata/query commands, lookup helpers, recursive/search/file-stream processors, `find`, application launchers, and script interpreters therefore never receive zero-resource admission from basename alone. Implementation-owned Git/diagnostic probes use their separate fixed-argv/internal boundary and do not depend on this allowlist. `resource-admission.mjs` persists per-user owner-only leases and waiters outside workspace state; ownership is bound to PID plus process-start identity and, where supported, the isolated process group. Releasing the caller-side lease does not delete a bound POSIX reservation while that isolated process group still exists: the persisted lease remains until ordinary pruning observes the group gone, so a detached descendant cannot become unaccounted merely because its direct caller returned. Independent nested process roots retain their own full durable leases for crash recovery, but live accounting does not blindly add an orchestration envelope and every child reservation. `resource-process-ancestry.mjs` samples the live parent graph through `resource-process-ancestry-cache.mjs`; the async cache coalesces concurrent requests and retains a completed snapshot for one second so admission retries do not repeatedly enumerate the complete process table. If ancestry cannot be sampled, accounting falls back to conservative full summation. `resource-lease-accounting.mjs` forms an ephemeral lease forest whose effective vector is the component-wise maximum of a node's own envelope and the sum of its direct lease children. A pending nested request is charged only for the additional vector it contributes to that forest. Same-key contention is exempted only for the request's actual ancestor lease chain; siblings and unrelated roots still serialize.
|
|
262
262
|
|
|
263
|
-
Live host-pressure observation is deliberately asynchronous-only. `resource-probe-command.mjs` owns bounded `execFile` child-probe transport and intentionally exposes no synchronous `spawnSync` path; `resource-host-darwin.mjs` runs Darwin memory, VM, disk, and thermal probes concurrently, `resource-process-ancestry.mjs` samples the parent graph asynchronously, and `resource-host-snapshot.mjs` composes those results without blocking the daemon event loop on `ps`, `memory_pressure`, `vm_stat`, `iostat`, or `pmset`. The previous sync host/Darwin/process-parent samplers and sync ancestry cache are removed rather than retained as test-only APIs that a future runtime path could accidentally call. Process-start identity used when binding a spawned lease is also sampled asynchronously before the coordinator file lock is taken. Lease/waiter pruning uses a separate one-second cached async process-start snapshot collected outside the transaction lock; while holding the lock it performs only
|
|
263
|
+
Live host-pressure observation is deliberately asynchronous-only. `resource-probe-command.mjs` owns bounded `execFile` child-probe transport and intentionally exposes no synchronous `spawnSync` path; `resource-host-darwin.mjs` runs Darwin memory, VM, disk, and thermal probes concurrently, `resource-process-ancestry.mjs` samples the parent graph asynchronously, and `resource-host-snapshot.mjs` composes those results without blocking the daemon event loop on `ps`, `memory_pressure`, `vm_stat`, `iostat`, or `pmset`. The previous sync host/Darwin/process-parent samplers and sync ancestry cache are removed rather than retained as test-only APIs that a future runtime path could accidentally call. Process-start identity used when binding a spawned lease is also sampled asynchronously before the coordinator file lock is taken. Lease/waiter pruning uses a separate one-second cached async process-start snapshot collected outside the transaction lock; while holding the lock it performs only PID liveness plus in-memory generation comparison. The currently executing coordinator is authoritative for its own generation and uses `currentProcessStartTimeMs()` when a persisted waiter/lease owner or atomic staging publisher PID equals `process.pid`; the external all-process snapshot remains generation authority only for foreign owners. Missing foreign snapshot evidence fails closed for a live PID, while a proven foreign generation mismatch remains reclaimable before isolated process-group liveness can preserve the lease. This prevents a still-running coordinator from self-pruning its own waiter or provisional lease, or deleting its own in-flight waiter/lease staging file, because of an external process-table generation discrepancy. Staging from an older same-PID generation remains reclaimable because its file timestamp predates the current process-start identity. The expired beta.104 transaction-owner directory migration path is no longer executable; unsupported legacy transaction-lock directories are retained unchanged and fail closed so stale migration code cannot become a destructive recovery surface. `resource-host-cache.mjs` treats a same-project general host snapshot as fresh for 500 milliseconds while retaining successful I/O evidence for at most five seconds; a stale general sample can therefore refresh cheap CPU/memory/load data without waiting for another one-second `iostat` interval, but an older throughput peak is not copied into a current quick sample. Timestamps represent sample completion rather than sample start. `resource-admission-policy.mjs` evaluates effective CPU, memory, I/O, disk-reserve, startup-window, and host-pressure values without imposing global serialism. macOS `iostat -Id` exposes transfer and throughput activity, not device utilization/saturation; fresh high IOPS/MB-per-second evidence therefore tightens I/O capacity as Yellow pressure but cannot by itself mark the host Red. Red remains reserved for direct critical evidence such as thermal/memory/disk-headroom failure or a severe load backlog corroborated by current CPU/I/O evidence. Linux additionally uses `MemAvailable` and optional PSI avg10 observations through `resource-host-linux.mjs`; sustained `psi_io_full_avg10 >= 60` is direct critical I/O-stall evidence and therefore Red, while lower I/O PSI remains a Yellow capacity-throttling signal. The soft free-disk floor is `min(80 GiB, max(8 GiB, 15% of volume))`; the post-reservation hard floor is `min(50 GiB, max(5 GiB, 10% of volume))`. Disk-only hard-red pressure has one narrow self-recovery exception: only an internally classified direct standard absolute deletion executable with the exact small `disk-reclaim` envelope may enter using Yellow capacity limits, and only when `disk_free_headroom_critical` is the sole critical reason. PATH-resolved or shell-composed deletion cannot claim that class, and thermal, memory, PSI, CPU/load, or other Red evidence still blocks it. Diagnostics expose observed busy CPU next to reserved CPU and their ratio as a mismatch signal, not as process attribution. `resource-admission-diagnostics.mjs` owns the privacy-safe snapshot/check projection, while `resource-admission-diagnostic-error.mjs` separately classifies bounded transaction/staging-lock contention as retryable coordinator-busy snapshot unavailability instead of inflating the diagnostics module or mislabeling contention as process execution failure. `resource-waiters.mjs` normally skips an older request that does not fit, but a sufficiently aged rank-zero waiter blocked by coordinator-owned project/CPU/I/O/memory capacity can enter a protected drain phase if it is structurally feasible after current leases disappear. Fixed implementation-owned probes bypass this coordinator so diagnostics/recovery remain usable during resource pressure.
|
|
264
264
|
|
|
265
265
|
The same protocol is intentionally language-neutral: compatible workflow controllers may use the same per-user coordinator root, schema, lease ownership, contention keys, and waiter ordering. This permits Machine Bridge and workflow-bundle to coordinate independent process trees without a new daemon. `resource-project-key.mjs` canonicalizes the existing filesystem path before deriving the v1 project-contention hash, so path aliases such as macOS `/var` versus `/private/var` and symlinked workspace roots cannot create different mutex identities across implementations; Windows additionally normalizes separator form, path case, and extended-path prefixes before hashing. Lease/waiter records contain resource families, numeric reservations, hashes, timestamps, and process ownership but never argv, shell text, or raw project paths. The anonymous `project_hash` is derived from the same canonical project identity as contention and is diagnostic correlation only; it is not authorization or admission authority. The coordinator is fail-closed on malformed persistent authority.
|
|
266
266
|
|
|
@@ -286,13 +286,13 @@ Managed jobs use the same argv/environment primitives but a different lifecycle.
|
|
|
286
286
|
|
|
287
287
|
Worker deployment is an explicit evidence state machine owned by `worker-deployment.mjs`. Wrangler upload is the authoritative remote write. Public `/healthz` is a subsequent read used to verify Worker identity and package version; it is not a transaction commit signal for the upload and does not attest the active device-authentication secret. The v5 deployment fingerprint is an HMAC over individually length-framed protocol marker, Worker name, file count, normalized relative paths, and bytes. Required Worker/shared/config inputs are traversed with `lstat`, and file bytes use bounded descriptor-first no-follow reads with multiple-hard-link rejection; absent, inaccessible, symlinked, special, or identity-changing inputs fail before upload. After a successful Wrangler result, local state atomically records the detected `workers.dev` URL, MCP URL, content/secret fingerprint, deployed package version, and timestamp before health verification begins. If verification is ambiguous, the next start compares the same fingerprint and performs a read-only verification rather than repeating the remote write. Owner-authorized candidate activation adds the missing end-to-end evidence: device preflight, signed challenge authentication, and readiness probing. An explicit authentication rejection after current-version health permits one same-name redeployment with the unchanged selected identity; ordinary timeout, proxy, TLS, network, and temporary health failures do not.
|
|
288
288
|
|
|
289
|
-
`worker-health.mjs` owns bounded health I/O: exact HTTPS `workers.dev` origin and Worker-name validation, environment-proxy selection, request timeout, redirect rejection, response-size limit, JSON/identity/version validation, and coarse error classification. `network-proxy.mjs` is shared by remote HTTP health probes and
|
|
289
|
+
`worker-health.mjs` owns bounded health I/O: exact HTTPS `workers.dev` origin and Worker-name validation, environment-proxy selection, request timeout, redirect rejection, response-size limit, JSON/identity/version validation, and coarse error classification. `network-proxy.mjs` is shared by remote HTTP health probes and relay construction. Worker health continues to honor the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` model. Relay WebSocket and signed HTTP fallback additionally accept the relay-only `MBM_RELAY_PROXY` override: a non-empty value selects that HTTP(S) proxy before standard environment resolution, while an empty value restores the standard model. Proxy URLs and credentials are never exposed. Local browser-broker health uses `loopback-health.mjs`, which accepts only canonical `http://127.0.0.1:<port>/healthz`, disables agent reuse, bounds the response, and deliberately bypasses environment proxies. Definitive stale evidence is retried for propagation and then permits a same-name redeploy; timeout, TLS, network, proxy, and temporary server failure remain ambiguous and fail without upload.
|
|
290
290
|
|
|
291
291
|
Worker-name mutation is a separate identity transition. Existing state rejects a different name unless the caller also supplies the explicit force option. An authorized transition clears the current URL/fingerprint and appends the prior validated name to bounded uninstall inventory. This prevents a health-retry workaround from silently becoming a new remote resource while preserving cleanup of intentional replacements.
|
|
292
292
|
|
|
293
293
|
## Daemon reconnect and replacement
|
|
294
294
|
|
|
295
|
-
The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to
|
|
295
|
+
The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to HTTP(S) CONNECT routing, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Normal relay selection uses standard environment-proxy resolution and honors `NO_PROXY`; a non-empty `MBM_RELAY_PROXY` instead fixes both WSS and signed HTTP fallback to that explicit proxy and deliberately bypasses `NO_PROXY`. Proxy connection failure does not authorize a direct relay attempt. The override controls only Machine Bridge's application-layer proxy hop: physical-interface binding, upstream proxy protocol, and upstream DNS belong to the selected sidecar/proxy implementation, which is why a loopback sidecar with independently bound outbound routing is the supported way to isolate relay continuity from an operating-system VPN/TUN. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
|
|
296
296
|
|
|
297
297
|
A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts liveness monitoring, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once authenticated, `RelayLiveness` sends a protocol-level WebSocket Ping every five seconds. The full ten-second Pong deadline begins only after the sender write callback proves the control frame left the local queue. Ping/application-send completions are fenced to the exact current WebSocket generation, so a callback from a superseded socket cannot mutate the replacement connection; a Ping callback that never completes has its own thirty-second local dispatch bound and becomes `relay_transport_send_timeout` even when the receive direction remains active. Reaching that first-stage deadline no longer immediately kills a ready WSS: `relay-transport-confirmation.mjs` opens one bounded fifteen-second application-confirmation window and emits the existing JSON heartbeat only after the connection is fully ready. A later protocol Pong or the explicit JSON application `pong` is bidirectional transport proof and clears suspicion; unrelated application messages update receive-side liveness only and cannot prove that daemon-to-Worker writes are succeeding. `ResilientRelayConnection` concurrently prewarms signed HTTPS in standby without Worker-side takeover; confirmed WSS recovery stops that prewarm, while a real disconnect upgrades it to exact-generation takeover and preempts any stale standby request. A scheduling-responsive true black hole therefore remains bounded to one five-second probe interval plus ten-second Pong response and fifteen-second independent confirmation, while a single ten-to-fifteen-second persistent-flow stall no longer becomes an avoidable reconnect storm. A detected local event-loop stall cancels remote suspicion and follows the separate recovery-grace branch rather than being counted as network failure. Transport Ping remains active during authenticated probing, but application heartbeat/confirmation is gated on verified readiness because the Worker probing state accepts only the readiness-probe result. Same-instance reconnect also performs explicit call-ownership reconciliation: the Worker sends its still-waiting IDs; the daemon snapshots the union of active calls and unacknowledged results, completes replacement-channel readiness, then returns `resume_calls_ack.missing_ids` only for IDs absent from that ownership union. Those IDs alone may receive one same-ID transport redelivery inside the original remaining deadline; if that cannot be done safely they settle retryably with `side_effects_started=false`. Active calls and retained terminal results continue on existing ownership, and no possibly executed tool call is automatically replayed. A separate JSON application heartbeat remains at twenty-five seconds and retains a seventy-five-second application-silence timeout, so protocol-level Pong cannot mask a Worker application path that has stopped responding. On the Worker, authenticated application-heartbeat activity refresh is synchronous and its JSON `pong` is queued before Durable Object alarm inspection or mutation; heartbeat and terminal-result paths then own one explicit coalesced alarm schedule. Persistent deadline work therefore cannot sit in front of application-liveness acknowledgement or be scheduled twice through a hidden touch helper. The Worker's ninety-second daemon-liveness deadline remains an independent wider fallback across Durable Object hibernation. Worker error codes `daemon_transport_error` and `daemon_liveness_timeout` are connection-recovery signals, not protocol incompatibility: they terminate only the current socket, run normal disconnect cleanup, and enter bounded reconnect. Local protocol-watchdog expiry is classified separately as `relay_transport_timeout`; local application-silence expiry retains `relay_heartbeat_timeout`. The same Worker classification is applied to the close frame if the preceding error frame is lost. Failure to send `hello`, or failure to deliver a readiness-probe result because its socket/session ended, follows the same transport-recovery path rather than being promoted to authentication or protocol failure. Unknown Worker errors, authentication rejection, malformed readiness sequencing, and identity/version mismatch remain fatal. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback. Each new authenticated hello also carries a schema-versioned, bounded relay diagnostic summary, including `previous_ready_inbound_silence_ms` so the silent half-open interval before close is not collapsed into the shorter close-to-ready outage duration. The local connection additionally keeps `recent_outages`, a newest-first in-memory ring capped at eight completed reconnect episodes. The ring contains only bounded outage numbers, first/final disconnect and ready timestamps, durations, close/error classes, prior-ready duration/silence, the first disconnect's fixed protocol-Ping/application-confirmation phase and millisecond ages, coarse application-route class, and connection-stage timings; a protocol/application Pong that clears transport suspicion without rebuilding WSS is not a completed outage and stays only in heartbeat diagnostics. Because the hello is sent before the recovering WebSocket can receive its final `ready_ack`, its carried ring cannot yet contain that current episode. The Worker therefore sanitizes the prior ring into the probing attachment and, only when promotion proves end-to-end readiness, synthesizes the now-completed current episode from the already bounded scalar fields before marking `outage_active=false`. Authenticated `server_info.daemon.relay_transport` can consequently retain several sub-warning-threshold reconnects instead of losing all but the latest one, without logging them by default, persisting a tool transcript, exposing endpoints/interfaces/DNS data, or treating near-miss liveness suspicions as outages.
|
|
298
298
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-08-31 beta.158 resource-coordinator self-identity release blocker
|
|
4
|
+
|
|
5
|
+
**The exact-main beta.157 source-release gate failed before any tag, GitHub Release, or npm publication mutation.** The canonical wrapper passed the first 122/131 frozen full-verification tasks, then `[123/131] full-access:test` failed on its first direct process with retryable `resource_admission`, `pressure_state=green`, and `admission_reason=fairness_wait`. That fixture already used a unique private coordinator root and a synthetic healthy-host sampler. A later standalone `full-access:test` passed, ruling out a deterministic full-profile/process capability defect but not making the release failure safe to ignore. Explicit remote reconciliation found no `v3.0.0-beta.157` tag and no GitHub Release, so the failed wrapper left no immutable source-publication object.
|
|
6
|
+
|
|
7
|
+
**The root cause is a coordinator self-identity race, not insufficient wait time or host pressure policy.** `ResourceCoordinator.acquire()` creates a waiter owned by the current coordinator process, then samples process-start generations before stale pruning. The stale-pruning helpers previously treated that asynchronous all-process snapshot as authoritative even for `pid === process.pid`. If the snapshot was generation-skewed for the running coordinator, its own just-created waiter could be misclassified as a reused process and removed. The direct admission decision could still be Green/admitted, but fairness selection would then no longer find the current waiter and would return `fairness_wait`. The same identity mistake applied to provisional leases owned by the coordinator itself and to atomic waiter/lease staging recovery: a snapshot that falsely reported the current PID as a later generation could make recovery delete a staging file still owned by the running publisher. Beta.158 uses `currentProcessStartTimeMs()` for the current PID across waiter liveness, lease liveness, and staging-publisher recovery; external snapshots remain authoritative only for foreign PIDs, where missing evidence remains fail-closed and a proven mismatch remains reclaimable. Direct A/B probes against the accepted beta.157 tree reproduce both production failures: a skewed current-PID generation turns a healthy single waiter into Green-state `fairness_wait`, and a future-generation snapshot silently deletes a live staging file. The same probes against beta.158 admit the waiter and return `MBM_RESOURCE_STAGING_BUSY` while preserving the staging file. Focused regressions also prove foreign PID reuse and a same-PID staging artifact predating the current process are still reclaimed. A targeted rerun additionally exposed one test-only timing oracle: the cancellation/fairness wake fixture used an approximately 200 ms observer window while still paying for a real all-process generation scan. That fixture now injects the already-known current generation so it measures resource-change wakeup semantics rather than host process-table latency; production fairness, retry, and probe deadlines are unchanged.
|
|
8
|
+
|
|
9
|
+
**Release consequence.** beta.157 had passed candidate activation, packaged OAuth canary, live runtime/service checks, and owner acceptance, but `prerelease:release` stopped before source publication. Because the resource-liveness implementation and regression are packaged bytes, beta.157 acceptance cannot be reused for the changed tree and package/runtime identity advances to `3.0.0-beta.158`; hosted tool schema generation remains 21. beta.158 requires its own frozen verification, candidate activation, owner acceptance, reviewed merge, GitHub prerelease, npm publication authorization, published activation, and soak evidence.
|
|
10
|
+
|
|
11
|
+
## 2026-08-31 beta.157 independent release-hermeticity, architecture, and privacy review
|
|
12
|
+
|
|
13
|
+
**The review treated the beta.156 release failures as evidence about the verifier rather than automatically converting them into runtime defects.** The first confirmed issue was ambient deployment state: a direct/default relay fixture inherited the owner daemon's persisted `MBM_RELAY_PROXY`, so correct beta.156 proxy selection changed the test's expected `tcp_connecting` path into `proxy_connecting`. A second independent fixture in Worker deployment had the same implicit environment assumption. Fixing one test locally was insufficient: the outer check runner could sanitize the service-only route while `coverage:test` spawned the same fixtures again from a fresh copy of `process.env`. Beta.157 therefore introduces one verification-child environment policy used by both runners. It removes owner runtime opt-ins that can redefine generic test behavior (`MBM_RELAY_PROXY`, `MBM_DEBUG`, `MBM_MACOS_BACKGROUND_VISUAL_BACKEND`, and `MBM_MACOS_TRUST_BROKER`) case-insensitively, while deliberately preserving ordinary proxy variables, execution-surface provenance, and explicit verification controls. Dedicated fixtures inject the removed settings explicitly. Hostile-parent runs that simultaneously configure an unreachable relay-only proxy, debug mode, the experimental native backend, an invalid trust broker, and the removed `MBM_PASS_ENV` switch pass both the 104-task fast gate and standalone critical coverage, proving the configuration cannot leak back through either verification layer.
|
|
14
|
+
|
|
15
|
+
**Three separate release-gate failures were scheduler or host-state oracles, not product invariants.** `resource-admission:test` first required a real system-wide process-parent probe to return a graph inside the production three-second bound even though production explicitly treats an unavailable graph as a conservative fallback. Under the owner's extreme Simulator/process-table load that assertion failed; the same `ps` later completed in roughly milliseconds after host cleanup. The test now injects and verifies the exact platform command/result grammar plus the conservative failure path. A later critical-coverage run exposed the same architectural mistake in its elastic admission retry/re-expansion fixture: host pressure, evaluator, retry sleep, and wall-state were synthetic, but the fixture still performed a real process-start snapshot inside a one-second retry budget. V8 coverage overhead could therefore consume that budget before the synthetic recovered-capacity decision. The fixture now injects the already-known current-process start generation; the same test passes with `NODE_V8_COVERAGE` enabled without changing the production admission deadline. `process-output-continuation-test` likewise used 100/450 ms output and 150 ms/2.2 s exit timers, then interpreted a particular scheduling order as proof of cooldown semantics. Independent coverage reruns exposed different assertions in that same timing family. Cooldown arm/preservation, output wakeup, `wait_for_exit` ignoring ordinary output, and terminal clearing are now driven by synthetic session state and explicit waiter notifications. The remaining real children verify only the hosted default clamp and execution-surface propagation; their test-only spawn seam clears inherited `NODE_V8_COVERAGE` from those `node -e` children while the parent test and production modules remain instrumented, so nested coverage overhead cannot become timing evidence. A direct V8-coverage run of the updated process-output fixture passes. The clamp assertion keeps only its scheduling-tolerant lower bound and no scheduler-dependent upper bound.
|
|
16
|
+
|
|
17
|
+
**The next frozen full run exposed a distinct stdio observer-budget defect after the repaired coverage gate had passed.** The full plan completed critical coverage, package verification, and consumer installation before `stdio:integration-test` timed out waiting for JSON-RPC id 6. That request was a real `run_process` with `timeout_seconds: 5`, while the test harness also used five seconds as the entire response deadline. Production intentionally starts the execution timer only after resource admission, so the harness was narrower than the contract it was measuring and could fail under host pressure before the process result or timeout settled. The stdio suite now retains the five-second default for ordinary protocol/read assertions but uses a named 20-second response budget for real `run_process`, `exec_command`, and `start_process` acceptance paths. Product admission and execution deadlines are unchanged. A direct stdio integration rerun passes, and the release-contract suite pins the separation so future refactors cannot collapse observer and execution clocks again.
|
|
18
|
+
|
|
19
|
+
**A subsequent frozen full run exposed one more nested-instrumentation oracle inside managed-job recovery coverage.** The plan reached 124/131 successfully and failed only when `coverage:test` reran `managed-jobs-test`: the resource-replacement recovery fixture remained in `current_phase=resource_admission` until its ten-second ready-file observer expired. That fixture already used an isolated coordinator root and a test-only `ResourceCoordinator` subclass with a synthetic healthy-host sampler, but the dedicated runner still inherited `NODE_V8_COVERAGE`; production admission always samples process-start generations so stale leases/waiters fail closed, and child instrumentation latency could therefore consume a fixture window whose actual purpose is resource replacement/reconstruction. The repair is deliberately test-local: only `managedJobTestRunnerForCoordinatorRoot()` clears nested V8 coverage while retaining the private coordinator, production admission/recovery code, resource identity checks, and ordinary runner instrumentation elsewhere. A direct parent-instrumented `managed-jobs-test` rerun completes successfully, and an architecture contract pins that narrow isolation so production deadlines or resource-liveness semantics are not weakened to stabilize coverage.
|
|
20
|
+
|
|
21
|
+
**Privacy, logging, documentation, and supply-chain review found two unnecessary disclosure paths, one hidden environment-authority bypass, and no credential/history or dependency finding.** The GitHub Actions hardened-npm bootstrap printed its ephemeral absolute bin directory even though the path was already communicated through `GITHUB_PATH`; that output is removed and an architecture contract prevents it from returning. Independently, the top-level CLI `MBM_DEBUG=1` failure path printed a raw exception stack rather than using the shared log-redaction boundary, so a local home path or credential-shaped value embedded in an exception could be emitted to stderr. Debug stack text now passes through `sanitizeLogText()`, and the CLI entrypoint regression proves both home-path and credential-pattern redaction while retaining useful debug failure output. More materially, `executionEnv()` still honored an undocumented initial-release `MBM_PASS_ENV=true` parent variable that promoted otherwise isolated execution to complete parent-environment inheritance. No documentation, test, current CLI option, or policy contract referenced that switch; it contradicted the documented rule that `full` owns complete parent inheritance while narrower profiles use isolation. The bypass is removed: only effective-policy/accepted-plan `fullEnv` can select complete inheritance, and a hostile-parent regression proves ambient `MBM_PASS_ENV` cannot leak an unrelated parent value into minimal mode while explicit `fullEnv` still does. Current normative documentation also retained two historical release labels (`Beta.61` in the threat model and a `Beta.58` testing heading), which are rewritten as current-contract language while genuinely actionable migration/evidence history remains versioned. Current-tree privacy scanning covers 817 tracked/unignored files with no findings; the independent reachable-history pass covers 5,095 reachable history blobs and 236 commit messages with no findings. Development and production npm audits report zero vulnerabilities; 117 registry signatures and 38 attestations verify. The broader catch/log scan found no additional fail-open cleanup path or raw credential/output logging boundary requiring a semantic change.
|
|
22
|
+
|
|
23
|
+
**The architectural pass identified low remaining growth headroom but did not manufacture a risky refactor to satisfy aesthetics.** `src/worker/index.ts` is effectively at its existing architecture budget (829 lines against an 830-line cap), while `src/local/computer-use.mjs` and `src/local/app-automation.mjs` are also close to their explicit limits. Those files already delegate substantial state domains, and the remaining large methods sit on security-sensitive WebSocket/relay or snapshot/action state machines. Raising line caps would hide the pressure, while mechanically splitting code without a coherent lifecycle boundary would worsen reasoning. This review therefore uses the new verification-environment module as a real responsibility extraction and records the larger modules as a constraint for the next functional change: extract an independently testable lifecycle/policy/verification domain before adding responsibilities. Existing architecture caps remain unchanged and continue to fail closed against silent growth.
|
|
24
|
+
|
|
25
|
+
**Package-footprint review found a separate repository-versus-consumer boundary that should be redesigned deliberately rather than cleaned opportunistically.** The beta.157 dry-run contains 582 files, about 5.53 MiB unpacked and 1.50 MiB packed; the historical `docs/AUDIT.md` and `CHANGELOG.md` alone account for roughly 1.4 MiB of unpacked text. This is not currently dead package content: `package:test` explicitly requires the audit record, while the changelog participates in release-impact, promotion-digest, GitHub-release, and project-context contracts. Beta.157 therefore preserves the existing package contract. A future packaging change should separate repository-only historical evidence from the concise current consumer/security record, update those release contracts explicitly, and measure install/package-size benefit rather than silently excluding files from `package.json.files`.
|
|
26
|
+
|
|
27
|
+
**Several plausible causes were explicitly falsified rather than accumulated as speculative fixes.** A controlled rerun showed `service-environment:test` does not unload or mutate the relay-egress sidecar; the earlier long beta.156 outage began when that external sidecar received an orderly stop and disappeared from launchd while the daemon stayed alive, then the original daemon recovered automatically after sidecar restoration. The beta.156 reconnect/privacy behavior therefore does not justify a new watchdog or direct fallback. The authority-revocation path was also rechecked after a surface-level catch looked suspicious; existing transactional queue persistence plus fail-closed daemon acknowledgement/replay already covers the mutation-versus-revocation durability boundary, so no duplicate security mechanism is added.
|
|
28
|
+
|
|
29
|
+
**A preliminary packaging check also caught a packaging-only defect in the new shared verifier module.** The file had been created owner-private as mode `0600`, while shipped source files must enter the npm archive as ordinary read-only source mode `0644`. The source mode is normalized to `0644`, and the focused package manifest test now passes with the complete 582-file package inventory. Because file mode participates in the exact source/package generation, no verification result from the earlier mode is reused as final evidence; the complete frozen full gate must run on the final tree.
|
|
30
|
+
|
|
31
|
+
**Release consequence.** The shared verification runner, coverage runner, hardened-npm bootstrap, execution-environment hardening, tests, and documentation are packaged bytes, so the source generation advances to `3.0.0-beta.157`; tool schema generation stays 21 because no MCP tool input/result or owner-visible diagnostic shape changes. Narrow-profile environment inheritance is intentionally tightened to the documented policy contract. beta.156 remains the immutable published artifact currently under soak. This audit does not fabricate beta.157 candidate, activation, acceptance, provider, npm, or soak evidence. A frozen full gate on the final beta.157 tree is required before candidate preparation; any later npm publication remains a fresh exact-version owner authorization boundary, and published beta.157 activation would restart the seven-day major-prerelease soak interval.
|
|
32
|
+
|
|
33
|
+
## 2026-08-31 beta.155 relay egress isolation review and beta.156 remediation
|
|
34
|
+
|
|
35
|
+
**Repeated short relay interruptions during beta.155 soak were traced below the Machine Bridge transport state machine rather than to daemon lifecycle, sleep, or resource pressure.** Privacy-bounded runtime diagnostics showed an awake operational daemon with healthy resource admission and no event-loop stall while the relay WebSocket received a connection reset. Operating-system network evidence at the same boundary showed broad flow churn on the active packet-tunnel route immediately before the relay socket closed, and an earlier interruption reproduced the same pattern. This is sufficient to classify the blocking failure as shared tunnel/upstream path churn; it does not identify a specific remote proxy node or claim that every future reset has the same cause.
|
|
36
|
+
|
|
37
|
+
**The existing standard environment-proxy support was necessary but not a complete isolation boundary.** `HTTPS_PROXY`/`HTTP_PROXY` can move CONNECT semantics into Machine Bridge, but a remotely addressed proxy socket is still opened through the operating-system route and can therefore remain inside the same VPN/TUN failure domain. Beta.156 adds `MBM_RELAY_PROXY` as a relay-only application proxy override shared by the preferred WebSocket and signed HTTP fallback. A non-empty override bypasses standard proxy/`NO_PROXY` selection, rejects non-HTTP(S) proxy schemes through the existing typed configuration boundary, and never authorizes direct fallback after proxy failure. Background service environment persistence includes the new key without logging its value. Worker deployment health deliberately remains on the standard proxy model so relay egress policy does not silently change deployment behavior.
|
|
38
|
+
|
|
39
|
+
**Physical-route ownership remains outside Machine Bridge.** The supported isolation topology is a loopback-only HTTP CONNECT sidecar whose own upstream proxy socket and resolver are pinned to the intended non-tunnel network path. Machine Bridge does not add another TUN, implement third-party proxy protocols, bind arbitrary interfaces itself, or claim that a remote `MBM_RELAY_PROXY` endpoint bypasses system routing. Regression coverage proves default WSS construction ignores `NO_PROXY` when the dedicated override is set, the real HTTP fallback traverses the same local CONNECT proxy, invalid dedicated schemes fail closed, and service installation retains the setting across background startup.
|
|
40
|
+
|
|
41
|
+
**Release consequence.** This is a blocking beta.155 soak defect and changes packaged local runtime, service-environment behavior, tests, and documentation. Package/runtime identity advances to beta.156 while hosted tool schema generation remains 21 because MCP tool schemas/results and owner-visible diagnostic shapes are unchanged. Beta.155 soak evidence cannot authorize stable promotion after this packaged fix; beta.156 requires fresh frozen verification, candidate/install-only proof, live activation and relay-egress observation, acceptance, publication under the beta channel, and a restarted major-prerelease soak interval. npm publication remains the separate explicit owner authorization boundary.
|
|
42
|
+
|
|
3
43
|
## 2026-08-30 beta.154 independent hosted-isolation/privacy review and beta.155 remediation
|
|
4
44
|
|
|
5
45
|
**The beta.154 release remained locally healthy under its existing verification plan, but the review found contract gaps outside that model.** A clean `main` baseline passed the 104-task fast plan, the complete 131-task plan, reachable-history privacy scanning, and both production/full npm audits with zero vulnerabilities. Independent delimiter-edge probes nevertheless proved that several free-form credential patterns used a terminal word boundary even though their token alphabet allows hyphen/underscore: valid-shaped GitLab, Google, and API-secret values ending in `-` could miss redaction entirely, while Slack/Machine Bridge/JWT cases could redact only a prefix and leave the final delimiter. The publication scanner independently carried parallel versions of the same common patterns, so both defenses could drift while their existing fixtures stayed green. Beta.155 moves common credential shapes into one shared source, adds current Machine Bridge password/OAuth/managed-job capability forms, uses alphabet-aware termination, and asserts exact normalized output/detection at delimiter edges.
|
package/docs/LOGGING.md
CHANGED
|
@@ -28,6 +28,8 @@ Foreground mode defaults to `info` and human-readable text. Platform autostart s
|
|
|
28
28
|
In JSON mode, every logger entry point—including direct level methods and the persistent daemon-ready transition—emits exactly one timestamped JSON object per line. No operational method may silently fall back to the human formatter.
|
|
29
29
|
Structured-field sanitizers use null-prototype intermediate maps, so prototype-shaped input keys such as `__proto__` and `constructor` remain ordinary bounded data instead of mutating the sanitizer object model. Sensitive key/value redaction is applied before serialization exactly as for other fields. Portable release/process diagnostics additionally redact credential-bearing URLs, npm `/auth/cli/<challenge>` identifiers, and sensitive query parameters such as token/code/state/session/OTP/verifier/proof/client-secret values before any bounded diagnostic text is emitted. Commands that may receive an npm Web OTP challenge capture raw upload output when no real TTY is present so the challenge URL cannot escape through inherited stdout/stderr before sanitization, and the npm publication lifecycle passes `--logs-max=0` so npm itself does not persist the challenge into its cache log directory. The internal logger exposes unsanitized terminal delivery only through explicitly named `rawPlain` / `rawJson` methods; those methods are reserved for command results whose purpose is to return exact user-owned values such as the initial one-time owner credential or requested local connection/path output, and must not be used for operational events, exceptions, or service logs.
|
|
30
30
|
|
|
31
|
+
The developer-only `MBM_DEBUG=1` top-level CLI failure diagnostic may include a bounded exception stack, but that stack still passes through the shared free-form sanitizer before stderr output. Debug mode therefore does not bypass credential-pattern, home-path, control-character, or message-length protections.
|
|
32
|
+
|
|
31
33
|
Human mode treats the message as the primary interface: it uses a natural-language explanation and includes only bounded diagnostic fields that add meaning. It does not repeat the machine event key. JSON mode retains the stable `event` field for ingestion and correlation. Routine `info`/`warn`/`error` events do not carry raw process IDs or managed-job IDs; exact ownership identity remains available through explicit status/diagnostic/job surfaces where it is the requested result. Event identifiers such as `relay.tool_result.discarded` are implementation contracts for structured logs, not text that should be shown as the warning itself.
|
|
32
34
|
|
|
33
35
|
| Level | Intended events |
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -40,7 +40,7 @@ machine-mcp doctor
|
|
|
40
40
|
machine-mcp --verbose
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Run these commands from the same environment used for startup so `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` are identical. After the foreground connection succeeds, run `machine-mcp service install` from that same PowerShell session. Installation stores only an allowlisted proxy/custom-CA environment snapshot in private local state so the logon task does not lose session-only `$env:` settings after reboot. A later start with no proxy variables does not erase the saved snapshot; set a variable explicitly to an empty value and reinstall to clear it. `machine-mcp service status` reports only the saved key names. Debug logs expose only the selected route and classified error; they never print a proxy endpoint or credentials.
|
|
43
|
+
Run these commands from the same environment used for startup so `MBM_RELAY_PROXY`, `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` are identical. After the foreground connection succeeds, run `machine-mcp service install` from that same PowerShell session. Installation stores only an allowlisted proxy/custom-CA environment snapshot in private local state so the logon task does not lose session-only `$env:` settings after reboot. A later start with no proxy variables does not erase the saved snapshot; set a variable explicitly to an empty value and reinstall to clear it. `machine-mcp service status` reports only the saved key names. Debug logs expose only the selected route and classified error; they never print a proxy endpoint or credentials.
|
|
44
44
|
|
|
45
45
|
### Worker quota controls
|
|
46
46
|
|
|
@@ -99,7 +99,11 @@ Brief retryable outages recover automatically. On a verified current daemon chan
|
|
|
99
99
|
|
|
100
100
|
A foreground MCP response is not durable delivery. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id` inside a 10-second acceptance budget; the Worker keeps a separate five-second settlement margin and adds principal-bound `recovery_key`/`control_key` capabilities to the hosted result. Preserve all three together: hosted `read_job` requires the read capability, hosted `cancel_job` requires the control capability, and a bare `job_id` is not remote recovery authority. If that acceptance response is lost to settlement timeout, HTTP response cancellation, or relay reconnect expiry after dispatch, the public error remains non-retryable for generic callers but carries the original key and the explicit recovery action `retry_same_tool_arguments_with_same_idempotency_key`; this reconciles against the retained job instead of authorizing a blind duplicate. The detached child may execute for up to 600 seconds after admission, but the managed runner can separately wait up to thirty minutes for cooperative machine-user resource admission before the child is spawned; the child execution deadline begins only after that admission succeeds. The shared ceiling is exposed machine-readably as `server_info.tool_delivery.managed_job_resource_admission_wait_max_ms`, because the same pre-spawn boundary applies to ordinary durable process jobs and owner `start_job` steps rather than to process tools alone. While the runner is in this pre-spawn state, `read_job.current_phase` is `resource_admission`; no command has started yet. An owner can correlate a long-running status at that phase with `diagnose_runtime.runtime.resource_admission` rather than interpreting it as a slow child process; a delegated non-owner should treat the phase itself as evidence that the child has not spawned, retain the same `job_id`, and avoid blind replay rather than attempting the owner-only machine-wide diagnostic. After admission, the phase returns to `steps`, `finally_steps`, or `recovery-cleanup` as appropriate. Completed step records preserve `duration_ms` as the total orchestration duration. Local/owner reads additionally expose `resource_admission_ms` as the pre-spawn portion so a delayed successful child can be distinguished from slow execution after the fact; delegated non-owner reads omit that machine-user scheduling timing rather than turning shared-host contention into a more precise cross-workload signal. The detached job survives MCP disconnect, relay reconnect, daemon restart, or service replacement. Non-owner process authority is unchanged: automatic durable execution still uses the delegated workspace sandbox and does not grant owner-only `start_job`. If a cached host schema omits a current required field, the Worker rejects before daemon dispatch with a normal no-side-effect tool error and requests a `tools/list` refresh rather than surfacing a protocol-only validation failure. Discovery instructions and tool descriptions both carry orchestration semantics, so `server/discover` and `tools/list` each advertise `ttlMs=0` and every host-visible tool description carries `Tool schema generation N`. `server_info.tool_delivery.tool_schema_generation`, `tool_schema_server_version`, `discovery_ttl_ms`, and `tool_list_ttl_ms` identify the live contract; `host_visible_schema_known_to_server=false` is equally important because a healthy new daemon/Worker cannot prove that an external host discarded an older cached action/tool snapshot. `host_turn_deadline_observable=false` means Machine Bridge cannot pre-compute the external assistant-turn deadline, while `managed_jobs_detached_from_mcp_response=true` records that an accepted durable job is not owned by that response lifetime. After an activation that changes hosted semantics, compare the live `server_info` generation and changed invocation behavior with the governed Workspace Action control snapshot when that product layer is applicable; automation may perform the supported refresh/review path without another conversational approval. Host-internal cache inspection is intentionally excluded from operational release verification. `start_process` remains the explicit daemon-lifetime path when interactive stdin or session-style incremental output is required, but hosted calls use a 10-second execution / 15-second settlement envelope and do not queue behind resource pressure: the first failed admission returns retryable `unavailable`; owner-local callers retain the cooperative wait. Hosted `read_process` supports paced same-response follow-up: each actual output/exit blocking wait lasts at most one second. If another would-block remote read arrives inside the fifteen-second blocking cooldown, the daemon keeps that same MCP call open until output/exit or the cooldown boundary rather than returning an immediate running checkpoint; the Worker reserves enough execution/settlement headroom for that server-side pacing. Results use `status_polling_mode=paced_followup` while the process remains live, plus `blocking_poll_throttled` and `next_blocking_poll_after_ms`; callers must not busy-loop and should respect that cooldown. A new hosted call waits at most fifteen seconds for daemon readiness, but that wait is charged against the call's existing execution budget; an in-flight disconnect likewise never pauses or extends the original absolute deadline. Pending-call reconnect retention is also bounded by the smaller of reconnect grace and that original remaining deadline, and diagnostics distinguish `original call deadline expired during reconnect` from a true full `reconnect grace expired` rather than labeling both cases as the latter. Owner-local stdio/CLI calls retain their synchronous local contract because they do not depend on a hosted response stream. Keep unrelated mutations and verification independently terminal, and never infer task success merely because a durable launch was accepted. For one coherent non-interactive sequence, prefer a repository umbrella command or multi-step `start_job` rather than creating many one-step durable process carriers. If the current task needs the result, hosted `read_job` may follow the known durable `job_id` with its preserved `recovery_key` repeatedly in the same assistant response until terminal state while calls continue to be accepted; active relay reads report `status_polling_mode=bounded_followup` and no longer recommend forced handoff. The normal hosted read is a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is the explicit immediate-checkpoint mode, while public hosted `wait_ms` is capped at 60 seconds. The default stays at 40 seconds because live host evidence showed that overlong single requests can outlive the host invocation even though the durable job itself remains healthy; beta.151 reproduced that class with a second explicit 180-second `read_job` returning `mcp_network_error` while generation-18 continuity evidence recorded zero unplanned ready-socket disconnects. The coalescing floor reduces host-visible event density and does not shorten the managed job, the assistant task, or the six-hour managed-step ceiling. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not use repeated `list_jobs`, `server_info`, or `diagnose_runtime` calls as substitute polling surfaces, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Hosted `list_jobs` is aggregate-only and intentionally cannot rediscover lost job IDs/names/recovery handles; detailed global inventory remains local CLI/stdio administration. Return the `job_id`, status, and current phase for later recovery only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint; only a terminal status is task-completion evidence.
|
|
101
101
|
|
|
102
|
-
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection.
|
|
102
|
+
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and ordinary relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. `MBM_RELAY_PROXY` is a relay-only override for deployments that need a stable egress boundary: when it is non-empty, both WebSocket relay construction and the signed HTTP fallback use that exact HTTP(S) proxy instead of consulting `HTTPS_PROXY`/`HTTP_PROXY` or `NO_PROXY`. A connection failure through the selected proxy follows normal relay recovery; it never retries the same attempt directly. Setting `MBM_RELAY_PROXY` explicitly to an empty value restores the standard environment-proxy model.
|
|
103
|
+
|
|
104
|
+
To isolate relay continuity from an operating-system VPN/TUN, point `MBM_RELAY_PROXY` at a loopback-only sidecar and configure that sidecar to bind its upstream proxy socket to the intended physical/network interface. Machine Bridge deliberately does not implement proxy protocols beyond HTTP CONNECT and does not bind the sidecar's upstream socket itself. Therefore a remote `MBM_RELAY_PROXY` value can still be routed through an operating-system tunnel, while a correctly bound local sidecar owns the route below Machine Bridge. DNS for the sidecar's upstream proxy hostname is likewise the sidecar's responsibility; use a resolver/path that does not re-enter the tunnel being isolated. Avoid a second system TUN solely for Machine Bridge, because competing default-route ownership recreates the same failure class at another layer.
|
|
105
|
+
|
|
106
|
+
Only HTTP and HTTPS proxy URLs are accepted. Invalid URLs or unsupported protocols fail startup with corrective guidance instead of entering the reconnect loop. Remote-owner `diagnose_runtime.runtime.relay.network_route` reports the resulting coarse application route, while local stdio `server_info.runtime.relay.network_route` reports `system-network-stack`, `application-http-proxy`, or `invalid-application-proxy-configuration`. The route remains `application-http-proxy` for either standard or dedicated proxy selection so diagnostics do not reveal proxy identity. This field describes only Machine Bridge application-level proxy selection: an operating-system VPN/TUN may still intercept `system-network-stack` traffic or the route to a remotely addressed proxy. `network_route_scope`, outage timestamps/durations, close category/code, transport error class, and next retry timing make that distinction explicit; proxy endpoints and credentials are never returned or logged. Worker deployment health continues to use standard `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY`; set those separately when the deployment-health path also requires a proxy. The browser-broker CLI health probe is a separate loopback-only path: it accepts only canonical `127.0.0.1`, uses direct Node HTTP with no proxy agent, and does not depend on `NO_PROXY`.
|
|
103
107
|
|
|
104
108
|
## Browser extension setup and diagnosis
|
|
105
109
|
|
package/docs/TESTING.md
CHANGED
|
@@ -26,10 +26,16 @@ Generation-9 continuity coverage additionally requires short hosted durable-proc
|
|
|
26
26
|
|
|
27
27
|
The fast runner avoids a second npm process for package scripts that are exactly one simple `node ...` invocation and have no `pre<task>`/`post<task>` lifecycle hooks. It recreates the relevant npm lifecycle identity in the child environment; scripts containing shell syntax, lifecycle hooks, or non-Node commands still run through npm, so shell/package-manager semantics are not guessed. Parallel-safe fast tasks use a bounded worker pool that defaults to at most four workers and is further bounded by Node's `availableParallelism()`; `MBM_CHECK_CONCURRENCY` may explicitly select 1–16. Known process-heavy gates remain serial barriers. This preserves every fast-plan gate while removing repeated npm startup cost. In non-verbose plans ordinary successful child output remains suppressed, but `self-test` forwards only its sparse phase-start/phase-complete markers so a hosted hang identifies the active phase before an outer CI deadline.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Verification children deliberately do not inherit owner runtime configuration that can change the behavior under test before a fixture opts in: `MBM_RELAY_PROXY`, `MBM_DEBUG`, `MBM_MACOS_BACKGROUND_VISUAL_BACKEND`, and `MBM_MACOS_TRUST_BROKER` are removed case-insensitively from both the ordinary check runner and nested critical-coverage children. Dedicated relay, debug, experimental-native-input, and trust-broker fixtures inject their required configuration explicitly; the opt-in live macOS smoke commands remain separate from the generic check plans. Standard `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` state, execution-surface provenance, and explicit verification controls such as `MBM_CHECK_CONCURRENCY` are not globally stripped because package/network tests, durable-carrier provenance, and verifier resource control legitimately depend on them.
|
|
30
|
+
|
|
31
|
+
Resource-admission regressions are first-class fast and critical-coverage gates. `resource-admission:test` covers Green/Yellow/Red pressure, absent optional pressure metrics, Linux `MemAvailable`/PSI parsing including default-reader/no-PSI behavior and sustained `psi_io_full_avg10 >= 60` as direct critical I/O-stall evidence, soft-versus-hard free-disk headroom, startup-window reservations, adaptive quick sampling, five-second I/O evidence reuse, bounded recovery when the atomically replaced cross-process host-sample cache changes generation during secure pathname verification, fail-closed exhaustion for persistent generation churn, valid and invalid persistent request contracts, stale ownership, priority aging, protected-queue diagnostics including the privacy-safe `drain_active` fairness signal, ordinary fit-aware head-of-line bypass, protected drain for an aged feasible waiter, impossible-old-request bypass, and fixed CPU fan-out above the best-case launch window. The latter proves an eight-worker fixed request on an idle eight-core interactive host fails immediately and non-retryably as `cpu_request_exceeds_launch_window`, a seven-worker request remains admissible, the impossible waiter cannot reserve starvation drain, and no retry sleep occurs. It also covers literal-shell-cwd versus dynamic-cwd contention scope, false heavy classification from ordinary filename arguments, and referenced wait timeout/in-flight abort/already-aborted behavior. It further proves nested lease-envelope accounting: parent coverage, child excess delta, parallel sibling summation, parent-loss recovery to full child cost, recent-CPU delta, same-key ancestor-only exemption, same-key sibling serialization, ancestry-sampling fallback, and a real coordinator acquisition where the requesting process is the live parent lease owner. Process-lifecycle coverage binds a real isolated group, releases the caller-side handle while that group is still alive, requires the durable reservation to remain, then proves ordinary pruning removes it after the group exits. Resource host, process ancestry, and resource-owner generation sampling is production-async-only: the old synchronous `sampleResourceHost`, Darwin sampler, process-parent sampler/cache, and `spawnSync` resource-probe transport are not retained as dormant APIs. Async host-probe tests require Darwin pressure probes to launch concurrently and keep a timer runnable while their child commands are pending; ancestry-cache tests require concurrent callers to share one in-flight system-wide process scan, refresh after the bounded TTL, and cache a failed/null sample only for that same TTL. Process-start snapshot tests require concurrent/current coordinator pruning to consume lock-external generation evidence, fail closed when a live foreign PID is absent from the snapshot, and reclaim a proven foreign PID reuse before numeric process-group liveness can override it. The coordinator's own `process.pid` is a separate identity boundary: waiter/provisional-lease pruning and atomic staging-publisher recovery must use `currentProcessStartTimeMs()` for that PID and must not let a mismatched external snapshot self-prune the running coordinator into `fairness_wait` or delete a live waiter/lease staging file; mismatched foreign-PID evidence remains reclaimable, missing foreign evidence remains fail-closed, and a same-PID staging artifact whose timestamp predates the current process generation is still reclaimed. The parallel waiter cancellation/wakeup fixture also injects the known current-process generation so its short observer window measures resource-change signalling rather than a real all-process scan. The expired beta.104 transaction-owner directory migration branch is no longer a current test surface. Script-classification regressions distinguish the actual executed entrypoint from later arguments: direct Node/Python coverage/verify roots and package-manager `check` scripts promote before fan-out, while interpreter module/eval modes and harmless scripts followed by heavy-looking filenames remain adaptive. Generic zero-resource admission is identity-bound: the standard absolute executables enumerated by `resource-light-command.mjs` qualify, and the exact current Node runtime receives the same light classification only for the argument-only `--version`/`-v` probe. All generic light invocations also require the hardened startup environment to exclude Node preload/profiler/cache overrides, TLS/key-log overrides, and POSIX/Darwin dynamic-loader injection variables; otherwise they remain adaptive. Arbitrary Node eval/scripts, lookalike Node paths, and the same `ps`/`uptime`/`sleep` basenames reached through PATH remain adaptive. `stat`, `df`, `which`, `printf`, `wc`, search/traversal commands, caller-controlled Git, `find`, `open`, and `osascript` remain adaptive because filesystem behavior, input size, options, helpers, state mutation, or child execution can make them unbounded. Arbitrary zsh/bash/sh/PowerShell/cmd invocations never receive the generic light bypass, including payloads that only appear to call a trusted inner executable. Background-priority tests require POSIX heavy background roots to use one `nice +5` wrapper and leave interactive roots unchanged. Canonical project-key tests require fixed v1 golden vectors plus a real path and a symlink alias to derive the same contention key. `resource-build-root:test` covers Cargo/SwiftPM/Xcode path/job controls, explicit override preservation, macOS no-index behavior, canonical cache identity, and the transaction rule that build-root preparation failure releases an already-acquired lease. `coverage:test` executes the direct resource fixtures and enforces dedicated thresholds across admission, hierarchy/coordinator accounting, process ancestry, canonical project identity, policy, waiters, profile/shell parsing, host pressure, wait liveness, staging, build-root/process admission, and request validation. Its V8 merge preserves explicitly covered ranges while letting a missing child range inherit the nearest enclosing execution count, and an exact-generation digest fails the run if source/test content, file mode, or symlink topology changes while coverage is being collected. Architecture release contracts prevent those fixtures, merge semantics, generation guard, or core threshold keys from disappearing. Runtime/managed-job suites separately prove leases bind real process trees and release through close, cancellation, timeout, and detached-job cleanup. `full-access:test` uses an isolated resource coordinator, a synthetic healthy-host sampler, and an explicit ten-second process-admission budget for its internal `LocalRuntime`; its separate detached-job observation window remains five minutes. Settlement polling imports the production managed-job terminal classifier rather than maintaining a second status list, so recovered/expired/cancelled-before-start terminal states cannot become five-minute false hangs. `local-self-test` keeps process-admission behavior deterministic instead of inheriting shared-host pressure: its runtime fixture uses an isolated coordinator, a synthetic healthy-host sampler, and a ten-second resource-admission budget on both one-shot and process-session boundaries. Its resource/managed-job CLI phase likewise avoids host pressure as a clock: execution-only jobs use the exact current-runtime `node --version` light probe, managed-job settlement shares one five-minute phase wall-clock budget, and only one terminal CLI `job read` is needed after direct persisted-state settlement. Daemon-fixture child exit waits are independently bounded at thirty seconds, and each self-test phase emits start/completion markers that the non-verbose verification runner forwards selectively so a hosted hang identifies its phase before an outer CI timeout. The five-minute resource-CLI phase bound and stdio detached-job observation window are test-harness ceilings, not production managed-job or user-task limits. Runtime/job command deadlines still start after admission, while ordinary production one-shot calls derive admission from 20% of their execution timeout with a two-second floor, ten-second ceiling, and never beyond the execution timeout. Owner-local process-session startup defaults to a ten-second cooperative wait; relay-origin `start_process` defaults to zero admission wait, performs one real admission decision, and returns retryable `unavailable` before spawn when pressure blocks it. Managed-job child steps default to ten minutes, may explicitly request up to six hours (`timeout_seconds=21600`), and start that execution timer only after resource admission. The runner's separate pre-spawn admission wait is capped by the shared thirty-minute durable-delivery contract; `read_job.current_phase=resource_admission` distinguishes that state from child execution, local/owner completed-step reads expose `resource_admission_ms` alongside total `duration_ms` while delegated non-owner reads omit that machine-user scheduling timing, and the release-contract suite binds the phase transition, owner-only timing projection, plus lease cleanup if post-admission status persistence fails. `runtime-handlers:test` asserts that ordinary `LocalRuntime` composition leaves both process-service overrides unset so those dynamic defaults are not accidentally replaced by a constructor constant. The resource-executing runtime in `agent-context-test` likewise uses an isolated coordinator, synthetic healthy-host sampling, and a ten-second test-only resource-admission budget; an architecture release contract prevents that deterministic isolation or short-bounded override from being silently removed.
|
|
32
|
+
|
|
33
|
+
The process-parent probe contract is tested through injected platform command results, including the conservative `null` fallback when the bounded OS probe fails. The unit/coverage gate does not require the developer or CI host's real process table to complete inside the production probe deadline; host-pressure behavior is exercised by the dedicated resource-admission and runtime diagnostic boundaries instead. The elastic admission retry/re-expansion fixture similarly injects its known current-process start generation so its one-second synthetic retry budget measures admission state transitions rather than a real system-wide process-generation scan, including under `NODE_V8_COVERAGE`. Likewise, remote process-read exit and cooldown state are pinned deterministically with synthetic session state and explicit waiter notifications, including output wakeup, `wait_for_exit` ignoring ordinary output, cooldown preservation, and terminal clearing. The few remaining real child-process fixtures use a test-only spawn seam that clears inherited `NODE_V8_COVERAGE` from their `node -e` children while leaving the parent test and production modules instrumented; this keeps nested coverage overhead out of child scheduling semantics without changing execution-surface propagation. Those fixtures retain only the hosted one-second wait clamp with a scheduling-tolerant lower bound rather than using sub-second child timers or scheduler-dependent upper bounds as correctness evidence.
|
|
34
|
+
|
|
35
|
+
The full-only stdio integration keeps its ordinary protocol/request response timeout at five seconds so discovery, schema, validation, and read-only regressions still fail promptly. Requests that deliberately cross the real process-admission boundary use a separate 20-second `PROCESS_TOOL_RESPONSE_WAIT_MS` harness budget. This is an observer ceiling, not a product timeout change: a five-second `run_process`/`exec_command` execution may first consume the documented local resource-admission window, while `start_process` may wait up to the local process-session admission window before spawn. The integration therefore must not use the product execution timeout itself as the total request/transport/settlement observation deadline.
|
|
30
36
|
Managed-job runner-log bounding is likewise isolated from host scheduling and suite-wide coverage instrumentation: the `bounded runner diagnostics` fixture uses a dedicated raw manager/root with `minimalEnv:true`, asserts its reviewed plan has `full_env=false` and no `NODE_V8_COVERAGE` step override, then launches the exact current-runtime `node --version` probe after pre-filling the runner log. This keeps the production rule intact that any Node instrumentation/preload environment makes a nominal version probe adaptive, while ensuring this test measures launch-time log trimming rather than shared CI pressure or coverage transport. The general managed-job test wrapper follows the same production boundary: it injects an empty `NODE_V8_COVERAGE` override only for `minimalEnv:false` fixtures that would otherwise inherit the parent coverage stream; `minimalEnv:true` fixtures receive no synthetic Node instrumentation key because their runner execution environment already strips it. The dependency-retention fail-closed setup likewise uses exact current-runtime `node --version`: it only needs a persisted successful upstream result before corrupting the dependent plan and therefore must not inherit unrelated host-pressure timing from an adaptive no-op. Same-daemon dependency-wait runner recovery is verified without a test-side `read_job` of the recovered upstream: persisted runner state must show one autonomous relaunch within a forty-second fixture window that covers all three 10.1-second production reconciliation attempts plus scheduler margin, then an independently waiting downstream must report a structured dependency failure identifying that recovered upstream as `failed/dependency_failed`.
|
|
31
37
|
|
|
32
|
-
Managed-job integration fixtures that genuinely need adaptive script semantics use a test-process-only runner launch seam instead of treating hosted machine pressure as part of those lifecycle assertions. `createManagedJobTestManager()` supplies a constructor-only `runnerSpawnProcess` function; it adds a Node `--import` hook to the runner process only, passing the hook as a canonical `file://` URL so Windows drive-letter paths cannot be misparsed as URL schemes. The hook redirects exactly the production runner's `./resource-admission.mjs` import to a fixture coordinator that subclasses the production implementation and changes only `sampleHost` to the shared synthetic healthy-host sampler. The hook is not inherited by managed-job children, is not expressible through MCP plans or environment settings, and is absent from `LocalRuntime` production construction. Dedicated resource-admission tests remain responsible for Green/Yellow/Red pressure and scheduling behavior.
|
|
38
|
+
Managed-job integration fixtures that genuinely need adaptive script semantics use a test-process-only runner launch seam instead of treating hosted machine pressure as part of those lifecycle assertions. `createManagedJobTestManager()` supplies a constructor-only `runnerSpawnProcess` function; it adds a Node `--import` hook to the runner process only, passing the hook as a canonical `file://` URL so Windows drive-letter paths cannot be misparsed as URL schemes. The hook redirects exactly the production runner's `./resource-admission.mjs` import to a fixture coordinator that subclasses the production implementation and changes only `sampleHost` to the shared synthetic healthy-host sampler. The hook is not inherited by managed-job children, is not expressible through MCP plans or environment settings, and is absent from `LocalRuntime` production construction. The resource-replacement recovery fixture adds one narrower test-only boundary on top of that seam: its dedicated runner receives `NODE_V8_COVERAGE=""` while the parent managed-job suite and ordinary runner fixtures remain instrumented, so the fixture's ten-second readiness observer measures resource materialization/recovery rather than nested coverage latency in the production process-start generation sampler. Dedicated resource-admission tests remain responsible for Green/Yellow/Red pressure and scheduling behavior.
|
|
33
39
|
|
|
34
40
|
The Wrangler command lifecycle fixture waits for the child `close` event before its test body settles, then removes its unique temporary root with Node's bounded recursive-removal retry support (`maxRetries: 5`, `retryDelay: 50`). This addresses transient Windows `EBUSY` and related filesystem lock windows after process/stdio settlement without inserting a fixed sleep or changing the production Wrangler command timeout and cleanup lifecycle.
|
|
35
41
|
|
|
@@ -262,7 +268,7 @@ Every candidate closeout requires fresh fast and full plans after the final pack
|
|
|
262
268
|
|
|
263
269
|
`mcp-controller:test`, `mcp-response-proxy:test`, `worker-runtime-infrastructure:test`, and Worker integration jointly prevent client-visible session/replay state from returning. They require bounded in-memory pending-call admission, current per-request metadata, deterministic response-reader/request-abort cancellation, unguessable internal cancellation capabilities, same-daemon transient rebind, absence of persisted MCP delivery budgets, and negative coverage for removed session/recovery headers. `ci-bootstrap:test` separately executes the hardened npm bootstrap in a synthetic fresh checkout with no `node_modules`, proving that pre-install proxy validation is reachable without package imports.
|
|
264
270
|
|
|
265
|
-
###
|
|
271
|
+
### Browser pairing and bootstrap security regressions
|
|
266
272
|
|
|
267
273
|
The browser suite covers the final bootstrap and already-paired authentication contracts end to end: process-owned ephemeral pairing listener, fragment stripping before extension messaging, init-HMAC-before-pending-allocation, server proof before WebSocket upgrade, one-time proof consumption, replay idempotence, current schema-2 persisted-state validation, and fail-closed previous-owner pairing migration. `browser-broker-auth:test`, `browser-pairing-content:test`, `browser-pairing-launch:test`, `browser-cli-pairing:test`, `browser-service-worker:test`, `browser-identity:test`, and `browser-bridge:test` are all in the fast plan. The CLI pairing regression specifically requires `browser setup` to open the same ephemeral fragment bootstrap used by the MCP pair action while keeping its printed `pairing_url` sanitized, and requires opener failure to close the listener.
|
|
268
274
|
|
package/docs/THREAT_MODEL.md
CHANGED
|
@@ -203,7 +203,7 @@ Managed jobs outlive MCP calls and daemon reconnects. Plans and resources are va
|
|
|
203
203
|
|
|
204
204
|
Application-level limits bound many requests and outputs. An authorized owner process can still exhaust CPU, memory, disk, network, or external service quotas.
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
The current runtime uses cooperative admission to reduce accidental cross-agent resource storms, protected backfill and Linux PSI pressure evidence, and project contention scoped to provable shell working directories. None of these is a hostile-process containment boundary. A same-user process can ignore the coordinator, forge unrelated workload outside Machine Bridge, deliberately escape a process group, or consume more than its advisory reservation. The coordinator therefore protects cooperating Machine Bridge/workflow roots from accidental CPU/I/O/memory/disk oversubscription and starvation; account/container/VM/cgroup controls remain required against malicious or uncooperative local code. Persistent coordinator records contain no command text or raw project path, but their existence and coarse resource-family/timing metadata are local side channels to the same OS user. PSI reports system/cgroup stall history, not which particular cooperating process caused it.
|
|
207
207
|
|
|
208
208
|
### System VPN/TUN and distributed activation
|
|
209
209
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.158",
|
|
4
4
|
"description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/scripts/check-runner.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { performance } from "node:perf_hooks";
|
|
3
3
|
import { BoundedOutput } from "../src/local/bounded-output.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { verificationChildEnvironment } from "./verification-environment.mjs";
|
|
5
5
|
|
|
6
6
|
const FAILURE_OUTPUT_BYTES_PER_STREAM = 64 * 1024;
|
|
7
7
|
const MAX_VERIFICATION_CONCURRENCY = 16;
|
|
@@ -167,7 +167,7 @@ function sparseLineForwarder(output, prefixes) {
|
|
|
167
167
|
|
|
168
168
|
function taskInvocation(task, packageScripts, npmCli, cwd, env) {
|
|
169
169
|
const cleanEnvironment = {
|
|
170
|
-
...
|
|
170
|
+
...verificationChildEnvironment(env),
|
|
171
171
|
NO_COLOR: env.NO_COLOR || "1",
|
|
172
172
|
};
|
|
173
173
|
const direct = directNodeInvocation(task, packageScripts);
|
|
@@ -5,11 +5,13 @@ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { captureCoverageGeneration } from "./coverage-generation.mjs";
|
|
7
7
|
import { mergeFunctionExecutions } from "./coverage-range-merge.mjs";
|
|
8
|
+
import { verificationChildEnvironment } from "./verification-environment.mjs";
|
|
8
9
|
|
|
9
10
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
11
|
const CRITICAL_SCRIPT_FILES = new Set(["scripts/release-publication-guard.mjs", "scripts/verification-generation-guard.mjs", "scripts/verification-state.mjs"]);
|
|
11
12
|
const generationBefore = captureCoverageGeneration(root);
|
|
12
13
|
const coverageDir = mkdtempSync(resolve(tmpdir(), "machine-bridge-coverage-"));
|
|
14
|
+
const coverageEnvironment = verificationChildEnvironment(process.env);
|
|
13
15
|
const tests = [
|
|
14
16
|
"tests/policy-test.mjs",
|
|
15
17
|
"tests/runtime-infrastructure-test.mjs",
|
|
@@ -72,6 +74,7 @@ const tests = [
|
|
|
72
74
|
"tests/computer-use-test.mjs",
|
|
73
75
|
"tests/computer-use-result-budget-test.mjs",
|
|
74
76
|
"tests/relay-connection-test.mjs",
|
|
77
|
+
"tests/relay-http-fallback-test.mjs",
|
|
75
78
|
"tests/managed-job-boundary-test.mjs",
|
|
76
79
|
"tests/managed-jobs-test.mjs",
|
|
77
80
|
"tests/account-admin-test.mjs",
|
|
@@ -89,7 +92,7 @@ try {
|
|
|
89
92
|
for (const test of tests) {
|
|
90
93
|
const run = spawnSync(process.execPath, [test], {
|
|
91
94
|
cwd: root,
|
|
92
|
-
env: { ...
|
|
95
|
+
env: { ...coverageEnvironment, NODE_V8_COVERAGE: coverageDir },
|
|
93
96
|
encoding: "utf8",
|
|
94
97
|
maxBuffer: 16 * 1024 * 1024,
|
|
95
98
|
});
|
|
@@ -18,7 +18,7 @@ try {
|
|
|
18
18
|
chmodSync(posixWrapper, 0o755);
|
|
19
19
|
writeFileSync(join(bin, "npm.cmd"), `@echo off\r\n"${cmdQuote(process.execPath)}" "${cmdQuote(prepared.cli)}" %*\r\n`);
|
|
20
20
|
writeFileSync(githubPath, `${bin}\n`, { flag: "a" });
|
|
21
|
-
console.log(`Prepared integrity-verified hardened npm ${prepared.version} (undici ${prepared.undiciVersion}; brace-expansion ${prepared.braceExpansionVersion})
|
|
21
|
+
console.log(`Prepared integrity-verified hardened npm ${prepared.version} (undici ${prepared.undiciVersion}; brace-expansion ${prepared.braceExpansionVersion})`);
|
|
22
22
|
} catch (error) {
|
|
23
23
|
let cleanupError = null;
|
|
24
24
|
try { rmSync(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }); }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { nestedNpmEnvironment } from "../src/local/npm-environment.mjs";
|
|
2
|
+
|
|
3
|
+
const VERIFICATION_ISOLATED_ENVIRONMENT_KEYS = new Set([
|
|
4
|
+
"MBM_DEBUG",
|
|
5
|
+
"MBM_MACOS_BACKGROUND_VISUAL_BACKEND",
|
|
6
|
+
"MBM_MACOS_TRUST_BROKER",
|
|
7
|
+
"MBM_RELAY_PROXY",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export function verificationChildEnvironment(environment = process.env) {
|
|
11
|
+
const result = nestedNpmEnvironment(environment);
|
|
12
|
+
for (const key of Object.keys(result)) {
|
|
13
|
+
if (VERIFICATION_ISOLATED_ENVIRONMENT_KEYS.has(key.toUpperCase())) delete result[key];
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
|
-
import {
|
|
3
|
+
import { proxyAgentForRelayHttp } from "./network-proxy.mjs";
|
|
4
4
|
|
|
5
5
|
export function postDaemonHttpRelay({ url, headers, body, timeoutMs, maximumResponseBytes, signal }) {
|
|
6
6
|
return new Promise((resolve, reject) => {
|
|
@@ -8,7 +8,7 @@ export function postDaemonHttpRelay({ url, headers, body, timeoutMs, maximumResp
|
|
|
8
8
|
const client = target.protocol === "https:" ? https : target.protocol === "http:" ? http : null;
|
|
9
9
|
if (!client) { reject(relayRequestError("daemon_http_invalid_url", "daemon HTTP relay URL must use HTTP or HTTPS")); return; }
|
|
10
10
|
let proxy;
|
|
11
|
-
try { proxy =
|
|
11
|
+
try { proxy = proxyAgentForRelayHttp(target.href); }
|
|
12
12
|
catch (error) { reject(error); return; }
|
|
13
13
|
let settled = false;
|
|
14
14
|
let timer = null;
|
|
@@ -2,13 +2,23 @@ import { HttpsProxyAgent } from "https-proxy-agent";
|
|
|
2
2
|
import { getProxyForUrl } from "proxy-from-env";
|
|
3
3
|
|
|
4
4
|
const HTTP_PROTOCOLS = new Set(["http:", "https:"]);
|
|
5
|
+
export const RELAY_PROXY_ENVIRONMENT_KEY = "MBM_RELAY_PROXY";
|
|
5
6
|
|
|
6
|
-
export function proxyAgentForWebSocket(webSocketUrl, proxyResolver = getProxyForUrl) {
|
|
7
|
+
export function proxyAgentForWebSocket(webSocketUrl, proxyResolver = getProxyForUrl, environment = process.env) {
|
|
7
8
|
const target = new URL(String(webSocketUrl));
|
|
8
9
|
if (target.protocol !== "ws:" && target.protocol !== "wss:") throw new Error("relay WebSocket URL must use ws or wss");
|
|
9
10
|
const lookupUrl = new URL(target);
|
|
10
11
|
lookupUrl.protocol = target.protocol === "wss:" ? "https:" : "http:";
|
|
11
|
-
return
|
|
12
|
+
return proxyAgentForRelayLookup(lookupUrl, proxyResolver, environment, {
|
|
13
|
+
errorCode: "relay_proxy_configuration",
|
|
14
|
+
subject: "relay proxy",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function proxyAgentForRelayHttp(httpUrl, proxyResolver = getProxyForUrl, environment = process.env) {
|
|
19
|
+
const target = new URL(String(httpUrl));
|
|
20
|
+
if (!HTTP_PROTOCOLS.has(target.protocol)) throw new Error("relay HTTP URL must use http or https");
|
|
21
|
+
return proxyAgentForRelayLookup(target, proxyResolver, environment, {
|
|
12
22
|
errorCode: "relay_proxy_configuration",
|
|
13
23
|
subject: "relay proxy",
|
|
14
24
|
});
|
|
@@ -23,9 +33,21 @@ export function proxyAgentForHttp(httpUrl, proxyResolver = getProxyForUrl) {
|
|
|
23
33
|
});
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
function proxyAgentForRelayLookup(lookupUrl, proxyResolver, environment, context) {
|
|
37
|
+
const explicitProxy = Object.hasOwn(environment || {}, RELAY_PROXY_ENVIRONMENT_KEY)
|
|
38
|
+
? String(environment[RELAY_PROXY_ENVIRONMENT_KEY] ?? "").trim()
|
|
39
|
+
: "";
|
|
40
|
+
if (explicitProxy) return proxyAgentForValue(explicitProxy, context);
|
|
41
|
+
return proxyAgentForLookup(lookupUrl, proxyResolver, context);
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
function proxyAgentForLookup(lookupUrl, proxyResolver, context) {
|
|
27
45
|
const proxyValue = String(proxyResolver(lookupUrl.href) || "").trim();
|
|
28
46
|
if (!proxyValue) return { agent: null, mode: "direct" };
|
|
47
|
+
return proxyAgentForValue(proxyValue, context);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function proxyAgentForValue(proxyValue, context) {
|
|
29
51
|
let proxyUrl;
|
|
30
52
|
try {
|
|
31
53
|
proxyUrl = new URL(proxyValue);
|
|
@@ -48,7 +48,7 @@ export function relayFatalMessage(category) {
|
|
|
48
48
|
return "remote relay protocol error; upgrade and redeploy both components, then restart the daemon";
|
|
49
49
|
}
|
|
50
50
|
if (category === "relay_proxy_configuration") {
|
|
51
|
-
return "remote relay proxy configuration is invalid; check HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
|
|
51
|
+
return "remote relay proxy configuration is invalid; check MBM_RELAY_PROXY, HTTP_PROXY, HTTPS_PROXY, and NO_PROXY";
|
|
52
52
|
}
|
|
53
53
|
if (category === "relay_device_session_expired") {
|
|
54
54
|
return "daemon device session expired; restart the daemon to obtain a fresh root-signed session certificate";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { inspectProcessInstance, processStartTimeFromSnapshot } from "./process-identity.mjs";
|
|
1
|
+
import { currentProcessStartTimeMs, inspectProcessInstance, processStartTimeFromSnapshot } from "./process-identity.mjs";
|
|
2
2
|
|
|
3
3
|
export function resourceLeaseOwnerStatus(lease, processStarts = null) {
|
|
4
|
-
|
|
4
|
+
const observedStart = lease.owner.pid === process.pid
|
|
5
|
+
? currentProcessStartTimeMs()
|
|
6
|
+
: processStartTimeFromSnapshot(processStarts, lease.owner.pid);
|
|
7
|
+
return resourceLeaseOwnerStatusFromStart(lease, observedStart);
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
export function resourceLeaseOwnerStatusFromStart(lease, observedStart = null) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { lstatSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { filesystemIdentity, sameFilesystemIdentity } from "./filesystem-identity.mjs";
|
|
4
|
-
import { inspectProcessInstance, processStartTimeFromSnapshot } from "./process-identity.mjs";
|
|
4
|
+
import { currentProcessStartTimeMs, inspectProcessInstance, processStartTimeFromSnapshot } from "./process-identity.mjs";
|
|
5
5
|
|
|
6
6
|
const STAGING = /^\.(?<target>(?<kind>lease|wait)_[a-f0-9]{32}\.json)\.(?<pid>[1-9][0-9]*)\.(?<nonce>[a-f0-9]{16})\.tmp$/;
|
|
7
7
|
const FINAL = Object.freeze({ lease: /^lease_[a-f0-9]{32}\.json$/, wait: /^wait_[a-f0-9]{32}\.json$/ });
|
|
@@ -71,7 +71,9 @@ function inspectOptional(file, label) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function stagingPublisherMayBeCurrent(pid, staging, processStarts = undefined) {
|
|
74
|
-
const options = {
|
|
74
|
+
const options = {
|
|
75
|
+
getProcessStartTime: (value) => value === process.pid ? currentProcessStartTimeMs() : processStartTimeFromSnapshot(processStarts, value),
|
|
76
|
+
};
|
|
75
77
|
const status = inspectProcessInstance({ pid, startedAt: staging.modified_at }, options);
|
|
76
78
|
return status.alive === true && status.reclaimable !== true;
|
|
77
79
|
}
|
|
@@ -107,7 +107,9 @@ function compareWaiters(left, right, now) {
|
|
|
107
107
|
function waiterIsStale(waiter, now, processStarts) {
|
|
108
108
|
if (Date.parse(waiter.expires_at) < now) return true;
|
|
109
109
|
const status = inspectProcessInstance({ pid: waiter.owner.pid, startedAt: waiter.enqueued_at,
|
|
110
|
-
processStartedAt: waiter.owner.process_started_at }, {
|
|
110
|
+
processStartedAt: waiter.owner.process_started_at }, {
|
|
111
|
+
getProcessStartTime: (pid) => pid === process.pid ? currentProcessStartTimeMs() : processStartTimeFromSnapshot(processStarts, pid),
|
|
112
|
+
});
|
|
111
113
|
return status.reclaimable === true;
|
|
112
114
|
}
|
|
113
115
|
|
package/src/local/shell.mjs
CHANGED
|
@@ -159,7 +159,7 @@ export function workspaceShellCommand(command) {
|
|
|
159
159
|
export function executionEnv(workspace, options = {}) {
|
|
160
160
|
// Minimal mode deliberately replaces user home/temp/cache locations so common
|
|
161
161
|
// toolchains do not inherit credential-bearing configuration by accident.
|
|
162
|
-
if (options.fullEnv
|
|
162
|
+
if (options.fullEnv) return { ...process.env, MBM_WORKSPACE: workspace };
|
|
163
163
|
const runtimeDir = options.runtimeDir ? path.resolve(String(options.runtimeDir)) : "";
|
|
164
164
|
if (!runtimeDir) throw new Error("minimal execution environment requires a runtime directory");
|
|
165
165
|
const runtimeHome = path.join(runtimeDir, "home");
|
package/src/worker/index.ts
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
closeWebSocketQuietly, daemonErrorCloseCode, isObjectRecord, rejectDaemonMessage,
|
|
62
62
|
sendWebSocketQuietly, trySendWebSocket,
|
|
63
63
|
} from "./websocket-protocol.ts";
|
|
64
|
-
const SERVER_VERSION = "3.0.0-beta.
|
|
64
|
+
const SERVER_VERSION = "3.0.0-beta.158";
|
|
65
65
|
const MCP_SERVER_INFO = mcpServerInfo(SERVER_VERSION);
|
|
66
66
|
const MAX_DAEMON_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
67
67
|
const DAEMON_RECONNECT_GRACE_MS = relayContract.reconnectGraceMs; const NEW_CALL_RECONNECT_GRACE_MS = relayContract.newCallReconnectGraceMs;
|