machine-bridge-mcp 1.2.11 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/README.md +10 -5
- package/browser-extension/manifest.json +2 -2
- package/docs/ARCHITECTURE.md +18 -10
- package/docs/AUDIT.md +25 -3
- package/docs/CLIENTS.md +2 -2
- package/docs/ENGINEERING.md +2 -2
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/LOCAL_AUTHORIZATION.md +111 -0
- package/docs/LOGGING.md +4 -4
- package/docs/MULTI_ACCOUNT.md +5 -5
- package/docs/OPERATIONS.md +30 -5
- package/docs/OVERVIEW.md +12 -8
- package/docs/PROJECT_STANDARDS.md +1 -1
- package/docs/RELEASING.md +3 -3
- package/docs/TESTING.md +5 -2
- package/docs/THREAT_MODEL.md +7 -6
- package/docs/UPGRADING.md +10 -6
- package/package.json +4 -2
- package/scripts/check-plan.mjs +2 -0
- package/scripts/local-release-acceptance.mjs +2 -2
- package/scripts/release-acceptance.mjs +7 -4
- package/src/local/account-admin.mjs +28 -3
- package/src/local/cli-approval.mjs +117 -0
- package/src/local/cli-options.mjs +8 -2
- package/src/local/cli.mjs +13 -2
- package/src/local/device-identity.mjs +108 -0
- package/src/local/operation-authorization.mjs +366 -0
- package/src/local/operation-risk.mjs +221 -0
- package/src/local/operation-state-lock.mjs +92 -0
- package/src/local/relay-connection.mjs +12 -5
- package/src/local/runtime-relay.mjs +13 -5
- package/src/local/runtime.mjs +13 -7
- package/src/local/state.mjs +9 -3
- package/src/local/tool-executor.mjs +4 -2
- package/src/local/worker-deployment.mjs +4 -3
- package/src/local/worker-secret-file.mjs +2 -1
- package/src/shared/admin-auth.d.mts +10 -0
- package/src/shared/admin-auth.mjs +36 -0
- package/src/shared/daemon-auth.d.mts +20 -0
- package/src/shared/daemon-auth.mjs +55 -0
- package/src/worker/account-admin.ts +73 -4
- package/src/worker/daemon-auth.ts +205 -0
- package/src/worker/daemon-sockets.ts +15 -2
- package/src/worker/index.ts +59 -10
- package/src/worker/nonce-store.ts +79 -0
- package/src/worker/oauth-controller.ts +11 -6
- package/src/worker/oauth-refresh-families.ts +172 -0
- package/src/worker/oauth-state.ts +48 -3
- package/src/worker/oauth-tokens.ts +49 -40
package/docs/OVERVIEW.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Machine Bridge is one local authority surface with two MCP transports. The architecture is organized around three independent questions:
|
|
4
4
|
|
|
5
|
-
1. **Who may request an operation?** Remote OAuth account identity and role, or the local process identity that launched stdio.
|
|
5
|
+
1. **Who may request an operation?** Remote OAuth account/client identity and role, or the local process identity that launched stdio.
|
|
6
6
|
2. **Which tools are exposed?** The shared tool catalog, policy profile, and account-role intersection.
|
|
7
|
-
3. **
|
|
7
|
+
3. **Is this remote effect currently authorized?** Direct authenticated-owner authority within the daemon ceiling, automatic delegated workspace-safe behavior, or an account/client-bound delegated capability lease.
|
|
8
|
+
4. **What can an authorized tool actually do?** The local runtime, workspace/path rules, operating-system user authority, and platform security controls.
|
|
8
9
|
|
|
9
10
|
Transport authentication does not create an operating-system sandbox. The local daemon executes with the authority of its OS user.
|
|
10
11
|
|
|
@@ -14,13 +15,14 @@ Transport authentication does not create an operating-system sandbox. The local
|
|
|
14
15
|
flowchart LR
|
|
15
16
|
HC[Hosted MCP client] -->|HTTPS + OAuth 2.1 / PKCE| W[Cloudflare Worker]
|
|
16
17
|
W --> DO[Durable Object room]
|
|
17
|
-
DO -->|
|
|
18
|
+
DO -->|P-256 device-authenticated WebSocket| R[LocalRuntime]
|
|
18
19
|
|
|
19
20
|
LC[Local MCP client] -->|stdio| S[stdio adapter]
|
|
20
21
|
S --> R
|
|
21
22
|
|
|
22
23
|
R --> PG[PolicyGate + account access]
|
|
23
|
-
PG -->
|
|
24
|
+
PG --> OA[OperationAuthorizer]
|
|
25
|
+
OA --> TE[ToolExecutor]
|
|
24
26
|
TE --> FS[Workspace file service]
|
|
25
27
|
TE --> PS[Process services]
|
|
26
28
|
TE --> MJ[Managed jobs]
|
|
@@ -40,9 +42,10 @@ flowchart LR
|
|
|
40
42
|
|
|
41
43
|
1. The Worker validates OAuth client, token, account state, role, resource binding, and MCP session state.
|
|
42
44
|
2. The Worker filters advertised tools by account role and the daemon-reported capability ceiling.
|
|
43
|
-
3. The Durable Object relays a bounded tool envelope over the authenticated daemon socket.
|
|
45
|
+
3. The Durable Object relays a bounded tool envelope carrying account and OAuth client identity over the device-authenticated daemon socket.
|
|
44
46
|
4. The local runtime revalidates account authorization, policy, call lifecycle, timeout, and cancellation.
|
|
45
|
-
5. The
|
|
47
|
+
5. The local transaction gate directly permits authenticated owner work within the daemon ceiling; delegated accounts receive automatic workspace-safe behavior or require a matching bounded capability lease for a high-impact effect.
|
|
48
|
+
6. The selected local service executes with the daemon OS user's authority.
|
|
46
49
|
|
|
47
50
|
The Worker never receives ambient filesystem or process authority. It receives only explicit request/result messages.
|
|
48
51
|
|
|
@@ -65,6 +68,7 @@ Local and Worker code consume these contracts. Generated references and drift te
|
|
|
65
68
|
`LocalRuntime` is an orchestrator, not a low-level implementation module. It composes:
|
|
66
69
|
|
|
67
70
|
- policy and account authorization;
|
|
71
|
+
- authenticated-owner direct authority plus delegated effect classification and local capability leases;
|
|
68
72
|
- call registration, timeout, cancellation, and observability;
|
|
69
73
|
- workspace and path services;
|
|
70
74
|
- direct and shell process execution;
|
|
@@ -78,9 +82,9 @@ Low-level responsibilities remain in focused modules. Architecture tests enforce
|
|
|
78
82
|
|
|
79
83
|
## State and lifecycle
|
|
80
84
|
|
|
81
|
-
Each canonical workspace has independent profile state, Worker identity, credentials, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
|
|
85
|
+
Each canonical workspace has independent profile state, Worker identity, credentials, local capability leases, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
|
|
82
86
|
|
|
83
|
-
The relay distinguishes
|
|
87
|
+
The relay distinguishes signed preflight, challenge authentication, readiness probing, and active service. A candidate daemon must prove possession of the enrolled P-256 key and complete an end-to-end probe before replacing an incumbent. Disconnect cancels relay-owned calls and terminates associated local processes.
|
|
84
88
|
|
|
85
89
|
Managed jobs use a separate durable lifecycle. Their plans are integrity-bound, runners are process-identity checked, transitions are lock-protected, terminal plans are scrubbed, and recovery is bounded.
|
|
86
90
|
|
|
@@ -20,7 +20,7 @@ Direct pushes to `main`, force pushes, and branch deletion are blocked by reposi
|
|
|
20
20
|
|
|
21
21
|
### Completion ownership and local acceptance
|
|
22
22
|
|
|
23
|
-
Repository automation owns implementation, local validation, observed candidate verification, acceptance recording, and pull-request completion. For every npm-package change, automation generates the exact candidate tarball with `npm run release:candidate` and stops before the first GitHub push. The repository owner explicitly authorizes the live candidate Worker update
|
|
23
|
+
Repository automation owns implementation, local validation, observed candidate verification, acceptance recording, and pull-request completion. For every npm-package change, automation generates the exact candidate tarball with `npm run release:candidate` and stops before the first GitHub push. The repository owner explicitly authorizes the live candidate Worker update in the active conversation. The coding agent starts the exact candidate through Machine Bridge with `npm run release:candidate:start -- --allow-worker-deploy`, verifies the deployed Worker version/hash, remote health, relay readiness, connected local version, and representative functionality, then records a passing decision with `npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp <version> CANDIDATE ON THE OWNER MACHINE AND IT WORKS"`. No owner terminal approval command is part of the standard workflow. Automated checks or an unobserved process are not sufficient evidence.
|
|
24
24
|
|
|
25
25
|
The tracked `release-acceptance/v<version>.json` record binds the observed result to the npm tarball SHA-1 and SHA-512 integrity value. The record is excluded from the package, so a content-preserving squash merge does not invalidate it; any source, executable script, package metadata, or packaged-documentation change produces a different package hash and requires a regenerated candidate and another observed live verification. `npm run github:push`, pull-request CI, and `npm run release` verify the record. Raw direct pushes of release-relevant branches are prohibited.
|
|
26
26
|
|
package/docs/RELEASING.md
CHANGED
|
@@ -40,13 +40,13 @@ A repository-only change under paths such as `.github/` may be merged without a
|
|
|
40
40
|
|
|
41
41
|
## Interactive local candidate acceptance
|
|
42
42
|
|
|
43
|
-
The repository owner
|
|
43
|
+
The repository owner explicitly authorizes the exact `.release-candidate/*.tgz` artifact and any configured same-name Worker update in the active conversation. The coding agent then runs the candidate startup helper through Machine Bridge:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
46
|
npm run release:candidate:start -- --allow-worker-deploy
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
The helper verifies the pending tarball hashes, installs it into the ignored `.release-candidate/runtime/` prefix without replacing the normal global installation, updates the configured same-name Worker when its version or deployment hash differs, and starts the installed candidate in the foreground. This is an in-place live candidate deployment, not an isolated staging Worker. The owner does not need to execute a terminal authorization command. The coding agent then verifies at minimum:
|
|
50
50
|
|
|
51
51
|
- the remote Worker reports the candidate version and the persisted deployment hash matches the candidate Worker bundle;
|
|
52
52
|
- the remote health route succeeds without redirect;
|
|
@@ -63,7 +63,7 @@ npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp 1.2.9 CANDIDA
|
|
|
63
63
|
|
|
64
64
|
This creates `release-acceptance/v<version>.json` containing package identity, npm tarball hashes, a portable Git-content digest, timestamp, result, and a fixed observed-verification marker. The portable digest is computed with a temporary Git index containing `HEAD` plus the complete working tree; the real staging area is not modified. The record does not store a personal name, machine path, command output, credential, or user content. It is intentionally excluded from the npm package, so adding it does not change the tested tarball.
|
|
65
65
|
|
|
66
|
-
The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. Automated tests alone, a prepared tarball, a process the agent did not observe, or an ambiguous owner response do not authorize acceptance. Version 1.2.8 owner-recorded acceptance
|
|
66
|
+
The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. Automated tests alone, a prepared tarball, a process the agent did not observe, or an ambiguous owner response do not authorize acceptance. Version 1.2.8 owner-recorded acceptance and the 1.2.9 owner-started marker remain supported as historical formats; version 2.0.0 and later require explicit owner authorization plus agent-operated, agent-observed verification.
|
|
67
67
|
|
|
68
68
|
## Push and review
|
|
69
69
|
|
package/docs/TESTING.md
CHANGED
|
@@ -19,7 +19,7 @@ The repository requires Node.js 26 and npm 12. `.node-version`, `.nvmrc`, `packa
|
|
|
19
19
|
The suite includes:
|
|
20
20
|
|
|
21
21
|
- package-impact classification derived from the npm package manifest, allowing repository-only GitHub workflow maintenance without weakening version requirements for shipped content;
|
|
22
|
-
- interactive candidate acceptance hashing, including owner-authorized in-place Worker deployment plus
|
|
22
|
+
- interactive candidate acceptance hashing, including owner-authorized in-place Worker deployment plus agent-operated candidate verification, exact tarball SHA-1/SHA-512 matching, descriptor-first no-follow reads, path-identity/symlink regressions, acceptance-record exclusion from the package, legacy 1.2.8 marker compatibility, and invalidation after any packaged-byte change;
|
|
23
23
|
- one-shot process output preview/continuation, completed-session paging, bounded nonzero-exit details, UTF-8-safe head/tail diagnostics, compact local/Worker MCP text mirrors, and quiet-success/bounded-failure verification-runner behavior;
|
|
24
24
|
- GitHub backlog enforcement that paginates all open issues and pull requests, permits only the current branch PR, and requires standard closing keywords for every open issue before a guarded push;
|
|
25
25
|
- release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
|
|
@@ -70,7 +70,10 @@ The suite includes:
|
|
|
70
70
|
- independently injected service CLI status/install/start/stop/uninstall/remove paths, including provider failure, no selected workspace, no deployed Worker, aliases, and default output/exit adapters;
|
|
71
71
|
- CLI parsing, policy profiles, and client configuration boundaries;
|
|
72
72
|
- live stdio MCP initialization with session instructions, capability resolution, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
|
|
73
|
-
-
|
|
73
|
+
- P-256 daemon identity generation, signed WebSocket preflight, one-time transactional nonce consumption, challenge transcript binding, wrong-key/tamper/expiry/replay rejection, and prevention of unauthenticated candidate churn;
|
|
74
|
+
- local operation-risk classification and catalog-wide review coverage; authenticated-owner direct execution; account/client-bound compound capability leases for delegated accounts; ordinary workspace automation; external-sensitive path composition; sensitive/persistence writes; symbolic-link ancestor and patch-move destination canonicalization; browser-session/application-control plus data-export composition; process/job continuation; scoped/full approval; cross-process daemon/CLI mutation serialization; expiry/revocation; owner-only persistence; and malformed-record rejection;
|
|
75
|
+
- signed account-administration requests with origin/method/path/body/time/nonce binding, transactional one-time nonce consumption, legacy bearer rejection, nonce replay rejection, and malformed nonce-state fail-closed behavior;
|
|
76
|
+
- live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, fifteen-minute access tokens, refresh-family idle/absolute limits, bounded consumed-token/revoked-family replay state, record-level schema validation, access/refresh rotation, stale refresh replay rejection with whole-family access/refresh revocation, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
|
|
74
77
|
- local runtime proof that one blocked tool handler does not serialize an independent handler, plus relay fault injection proving an undeliverable terminal result interrupts the ambiguous socket and enters reconnect backoff.
|
|
75
78
|
- a real headless-Chrome OAuth navigation regression with four cases: `form-action 'self'` blocks the first cross-origin callback, allowing only the registered callback blocks the regional redirect, allowing the registered and regional callbacks blocks the final Copilot Studio redirect, and the complete policy preserves `code` and `state` through all three cross-origin hops. Linux CI fails if Chrome is unavailable; other environments skip only this browser executable check while retaining the Worker CSP assertions.
|
|
76
79
|
|
package/docs/THREAT_MODEL.md
CHANGED
|
@@ -14,7 +14,7 @@ The main protected assets are:
|
|
|
14
14
|
|
|
15
15
|
- workspace and local-user-accessible files;
|
|
16
16
|
- process execution authority and inherited environment values;
|
|
17
|
-
- daemon
|
|
17
|
+
- daemon device private key, account-administration HMAC key, account password verifiers, OAuth authorization codes, access tokens, refresh tokens, and local capability leases;
|
|
18
18
|
- Worker and Durable Object routing state;
|
|
19
19
|
- owner-only profile, lock, resource, service, and managed-job state;
|
|
20
20
|
- registered local resource contents and paths;
|
|
@@ -30,7 +30,7 @@ The hosted client and its prompts, tools, extensions, and retrieved content are
|
|
|
30
30
|
|
|
31
31
|
### Worker to local daemon
|
|
32
32
|
|
|
33
|
-
The Worker is a relay and authorization layer, not a source of local authority. The daemon accepts only
|
|
33
|
+
The Worker is a relay and authorization layer, not a source of local authority. The daemon accepts only a version-compatible relay that proves possession of the enrolled P-256 device key in both a signed upgrade preflight and a Worker challenge. End-to-end readiness requires a local probe result through the active session. Every remote tool call carries bounded account and OAuth-client identity that is rechecked locally, followed by effect-level local transaction authorization.
|
|
34
34
|
|
|
35
35
|
### MCP host to local stdio process
|
|
36
36
|
|
|
@@ -50,7 +50,7 @@ State paths, locks, plans, resources, and service definitions are security-sensi
|
|
|
50
50
|
|
|
51
51
|
### Maintainer to release infrastructure
|
|
52
52
|
|
|
53
|
-
Repository checks reduce accidental or malicious package drift, but source hosting, npm ownership, protected environments, maintainer accounts, and human review remain external trust dependencies. Repository automation cannot manufacture independent review or claim live candidate success without observing the owner-
|
|
53
|
+
Repository checks reduce accidental or malicious package drift, but source hosting, npm ownership, protected environments, maintainer accounts, and human review remain external trust dependencies. Repository automation cannot manufacture independent review or claim live candidate success without observing the explicitly owner-authorized candidate. It may record acceptance after that observed verification.
|
|
54
54
|
|
|
55
55
|
## Attacker models
|
|
56
56
|
|
|
@@ -71,7 +71,8 @@ The implementation aims to preserve these invariants:
|
|
|
71
71
|
|
|
72
72
|
- deny unknown, malformed, stale, duplicated, unauthorized, or over-limit requests;
|
|
73
73
|
- intersect remote account authority with the daemon capability ceiling in both Worker and local runtime;
|
|
74
|
-
-
|
|
74
|
+
- permit authenticated owner automation directly within the daemon ceiling while binding delegated high-impact effects to local account/client-scoped, time-bounded capability leases;
|
|
75
|
+
- keep transport authentication, account/tool authorization, transaction authorization, and OS authority separate;
|
|
75
76
|
- use direct argv execution without shell interpretation unless the explicit shell tool is authorized;
|
|
76
77
|
- canonicalize confined paths and reject symlink-based write escape;
|
|
77
78
|
- bound request bodies, messages, files, output, logs, state, retained results, and concurrency;
|
|
@@ -102,7 +103,7 @@ Machine Bridge does **not** claim to provide:
|
|
|
102
103
|
|
|
103
104
|
### Canonical `full` profile
|
|
104
105
|
|
|
105
|
-
`full` intentionally
|
|
106
|
+
`full` intentionally preserves the complete catalog, unrestricted local-user paths, shell execution, parent environment, browser/application authority, and absolute path output. Version 2.0 treats the authenticated owner role as authorization to use that ceiling without a second terminal prompt. This prioritizes uninterrupted owner automation: compromise of an active owner OAuth client can exercise the complete daemon ceiling. Delegated accounts remain lease-bound for higher-impact effects. A malicious delegated client holding both valid OAuth credentials and an active matching lease can still act destructively within that lease. A temporary `full` lease is therefore an explicit automation window, not a sandbox or an endorsement of untrusted instructions. Use a narrower profile or a separate low-privilege OS boundary for mutually untrusted workloads.
|
|
106
107
|
|
|
107
108
|
### Application and browser automation
|
|
108
109
|
|
|
@@ -128,7 +129,7 @@ Code cannot create a second independent reviewer, npm OIDC trust relationship, p
|
|
|
128
129
|
|
|
129
130
|
The main regression suites cover:
|
|
130
131
|
|
|
131
|
-
- OAuth, account, policy,
|
|
132
|
+
- OAuth, refresh-family replay revocation, signed administration requests, account, policy, device-authenticated daemon readiness, relay replacement, and local capability leases;
|
|
132
133
|
- path confinement, atomic writes, state locks, service lifecycle, managed-job recovery, and destructive cleanup;
|
|
133
134
|
- direct process boundaries, shell separation, process-tree termination, timeout, cancellation, and disconnect;
|
|
134
135
|
- browser pairing, version/capability handshake, owner/client broker routing, sensitive-field handling, trusted input, and CSP navigation;
|
package/docs/UPGRADING.md
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
Machine Bridge does not retain parallel implementations for obsolete MCP protocol dates, policy revisions, state schemas, lock formats, or browser-extension protocols. The supported path is a direct upgrade from the immediately preceding published package while its state already uses the current schema.
|
|
6
6
|
|
|
7
|
-
Version
|
|
7
|
+
Version 2.0.0 is a coordinated Worker/daemon security-protocol upgrade. It replaces the long-lived daemon bearer with a P-256 device identity, replaces network account-administration bearer requests with per-request HMAC signatures, shortens OAuth access tokens, introduces refresh-token families and replay revocation, and adds local capability leases for high-impact remote effects. The Worker and daemon must converge on 2.0.0 together; the removed daemon bearer protocol is not retained as a fallback.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
On the first 2.0.0 state load, a workspace without a device identity generates one locally, removes the legacy daemon secret, and rotates the deployment-wide OAuth token version. Existing pre-2.0 access and refresh credentials therefore fail closed and every client must authorize again. The device private JWK remains in owner-only local state; only the public JWK is deployed to the Worker. Subsequent ordinary starts preserve both the device identity and token version.
|
|
10
|
+
|
|
11
|
+
The canonical `full` profile is unchanged as a capability ceiling. Authenticated owner operations use that ceiling directly without terminal approval. High-impact operations from delegated non-owner accounts require a local account/client-bound lease, while their normal workspace-contained reads and edits and project inspection remain automatic. Because the packaged extension controls an existing browser profile, one `browser-session` lease covers profile reads and actions; registered-resource input or file upload uses the separate `data-export` scope. An operator may approve the requested scope or explicitly open a temporary `full` window for at most eight hours. Lease state is independent of the policy profile and does not migrate into OAuth credentials.
|
|
10
12
|
|
|
11
13
|
Version 1.2.0 could accept prototype-shaped account roles through malformed administration input. On the first 1.2.1 or later Worker access, such an account is preserved for recovery but repaired fail-closed: its role becomes `reviewer`, it is disabled, its account version advances, and its authorization codes and tokens are removed. An operator can then assign a valid role, enable the account, and rotate its password through the normal account administration flow. A local policy record with an unknown profile label is normalized to `custom` while retaining its explicit capability fields; an invalid explicit `--profile` is rejected.
|
|
12
14
|
|
|
@@ -17,9 +19,11 @@ A state file from an older unsupported schema is rejected rather than guessed or
|
|
|
17
19
|
1. Finish or cancel ordinary interactive process sessions. Accepted managed jobs may continue independently, but inspect them before replacing the daemon.
|
|
18
20
|
2. Install the new package with the pinned npm procedure in [Getting started](GETTING_STARTED.md).
|
|
19
21
|
3. Run `machine-mcp doctor`.
|
|
20
|
-
4. Start `machine-mcp` normally for each workspace. Startup
|
|
21
|
-
5.
|
|
22
|
-
6.
|
|
22
|
+
4. Start `machine-mcp` normally in the foreground for each workspace. Startup generates the device identity if needed, rotates the old deployment token version, deploys the matching public key and 2.0.0 Worker, verifies end-to-end readiness, and only then may replace the prior service daemon.
|
|
23
|
+
5. Reauthorize every remote MCP client. Pre-2.0 access and refresh tokens are intentionally invalid.
|
|
24
|
+
6. Reload the unpacked browser extension. Protocol and packaged-version equality are mandatory; an old extension cannot replace a working compatible connection.
|
|
25
|
+
7. Exercise one safe read and one high-impact operation. Approve the resulting scope or an explicit temporary `--full` window through the local CLI, then retry the operation.
|
|
26
|
+
8. Restore or reinstall background service operation only after the foreground path is healthy.
|
|
23
27
|
|
|
24
28
|
## Upgrade safety
|
|
25
29
|
|
|
@@ -29,6 +33,6 @@ Machine Bridge never treats an unreadable or foreign-schema state file as empty
|
|
|
29
33
|
|
|
30
34
|
## Rollback
|
|
31
35
|
|
|
32
|
-
Rollback is supported only
|
|
36
|
+
Rollback is supported only from a complete pre-upgrade backup. Version 2.0.0 changes Worker/daemon authentication, OAuth refresh state, administration authentication, and local credential material. An older package cannot use the new device protocol or refresh-family state. Rolling back package files alone therefore produces an unavailable or incorrectly authenticated mixed system. Restore the complete owner-only profile backup, prior Worker secret set/build, package version, service definition, and browser extension as one unit, or fix forward.
|
|
33
37
|
|
|
34
38
|
Never roll back by copying only selected state files or changing version fields. Restore one complete verified state backup, package version, Worker build, and browser extension as a single operational unit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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",
|
|
@@ -133,7 +133,9 @@
|
|
|
133
133
|
"check-runner:test": "node tests/check-runner-test.mjs",
|
|
134
134
|
"process-output:test": "node tests/process-output-continuation-test.mjs",
|
|
135
135
|
"github-backlog:test": "node tests/github-backlog-test.mjs",
|
|
136
|
-
"github:backlog": "node scripts/github-backlog.mjs"
|
|
136
|
+
"github:backlog": "node scripts/github-backlog.mjs",
|
|
137
|
+
"operation-authorization:test": "node tests/operation-authorization-test.mjs",
|
|
138
|
+
"device-auth:test": "node tests/device-auth-test.mjs"
|
|
137
139
|
},
|
|
138
140
|
"dependencies": {
|
|
139
141
|
"https-proxy-agent": "9.1.0",
|
package/scripts/check-plan.mjs
CHANGED
|
@@ -10,6 +10,7 @@ export const FAST_CHECK_TASKS = Object.freeze([
|
|
|
10
10
|
"github-backlog:test",
|
|
11
11
|
"secure-file:test",
|
|
12
12
|
"worker-secret-file:test",
|
|
13
|
+
"device-auth:test",
|
|
13
14
|
"sarif-security:test",
|
|
14
15
|
"shell:test",
|
|
15
16
|
"architecture:test",
|
|
@@ -21,6 +22,7 @@ export const FAST_CHECK_TASKS = Object.freeze([
|
|
|
21
22
|
"commit-message:test",
|
|
22
23
|
"policy:test",
|
|
23
24
|
"account:test",
|
|
25
|
+
"operation-authorization:test",
|
|
24
26
|
"worker-oauth-controller:test",
|
|
25
27
|
"policy-docs:check",
|
|
26
28
|
"tool-docs:check",
|
|
@@ -53,9 +53,9 @@ function prepareCandidate() {
|
|
|
53
53
|
writeFileSync(candidateManifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
|
|
54
54
|
const phrase = confirmationPhrase(pkg.name, pkg.version);
|
|
55
55
|
console.log(`Release candidate created: ${join(candidateDirectory, metadata.filename)}`);
|
|
56
|
-
console.log("
|
|
56
|
+
console.log("After the repository owner explicitly authorizes this exact candidate in the active conversation, the coding agent starts it through Machine Bridge with:");
|
|
57
57
|
console.log("npm run release:candidate:start -- --allow-worker-deploy");
|
|
58
|
-
console.log("
|
|
58
|
+
console.log("The owner does not need to run a terminal authorization command. The coding agent keeps the candidate running while verifying connection readiness and representative functionality through Machine Bridge.");
|
|
59
59
|
console.log("After that observed live verification succeeds, the coding agent records acceptance with:");
|
|
60
60
|
console.log(`npm run release:accept -- --confirm \"${phrase}\"`);
|
|
61
61
|
console.log("Automated tests alone do not authorize acceptance or the first GitHub push.");
|
|
@@ -12,7 +12,9 @@ import { readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
|
|
|
12
12
|
export const ACCEPTANCE_SCHEMA_VERSION = 1;
|
|
13
13
|
export const ACCEPTANCE_POLICY_VERSION = "1.2.8";
|
|
14
14
|
export const AGENT_VERIFIED_ACCEPTANCE_VERSION = "1.2.9";
|
|
15
|
-
export const
|
|
15
|
+
export const AGENT_OPERATED_ACCEPTANCE_VERSION = "2.0.0";
|
|
16
|
+
export const ACCEPTANCE_CONFIRMATION = "owner-authorized-agent-operated-local-candidate";
|
|
17
|
+
export const OWNER_STARTED_ACCEPTANCE_CONFIRMATION = "owner-started-agent-verified-local-candidate";
|
|
16
18
|
export const LEGACY_ACCEPTANCE_CONFIRMATION = "repository-owner-local-test";
|
|
17
19
|
const MAX_ACCEPTANCE_BYTES = 64 * 1024;
|
|
18
20
|
const MAX_RELEASE_TARBALL_BYTES = 64 * 1024 * 1024;
|
|
@@ -22,9 +24,10 @@ export function requiresLocalAcceptance(version) {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export function acceptanceConfirmationForVersion(version) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
const parsed = parseVersion(version);
|
|
28
|
+
if (compareVersions(parsed, parseVersion(AGENT_OPERATED_ACCEPTANCE_VERSION)) >= 0) return ACCEPTANCE_CONFIRMATION;
|
|
29
|
+
if (compareVersions(parsed, parseVersion(AGENT_VERIFIED_ACCEPTANCE_VERSION)) >= 0) return OWNER_STARTED_ACCEPTANCE_CONFIRMATION;
|
|
30
|
+
return LEGACY_ACCEPTANCE_CONFIRMATION;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function acceptancePath(root, version) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
1
|
+
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
import { ADMIN_AUTH_SCHEME, adminAuthTranscript } from "../shared/admin-auth.mjs";
|
|
2
3
|
import { ACCOUNT_ROLES, normalizeAccountRole } from "./account-access.mjs";
|
|
3
4
|
import { BridgeError } from "./errors.mjs";
|
|
4
5
|
|
|
@@ -58,13 +59,21 @@ export class AccountAdminClient {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
async request(method, pathname, body) {
|
|
62
|
+
const serializedBody = body === undefined ? "" : JSON.stringify(body);
|
|
63
|
+
const headers = accountAdminRequestHeaders({
|
|
64
|
+
secret: this.adminSecret,
|
|
65
|
+
origin: this.workerUrl,
|
|
66
|
+
method,
|
|
67
|
+
pathname,
|
|
68
|
+
body: serializedBody,
|
|
69
|
+
});
|
|
61
70
|
const response = await this.fetchImpl(`${this.workerUrl}${pathname}`, {
|
|
62
71
|
method,
|
|
63
72
|
headers: {
|
|
64
|
-
|
|
73
|
+
...headers,
|
|
65
74
|
...(body === undefined ? {} : { "content-type": "application/json" }),
|
|
66
75
|
},
|
|
67
|
-
body: body === undefined ? undefined :
|
|
76
|
+
body: body === undefined ? undefined : serializedBody,
|
|
68
77
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
69
78
|
cache: "no-store",
|
|
70
79
|
}).catch((error) => {
|
|
@@ -80,6 +89,22 @@ export class AccountAdminClient {
|
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
|
|
93
|
+
export function accountAdminRequestHeaders({ secret, origin, method, pathname, body = "", now = Date.now(), nonce = randomBytes(24).toString("base64url") }) {
|
|
94
|
+
if (typeof secret !== "string" || secret.length < 24) throw new BridgeError("invalid_request", "account administration secret is missing");
|
|
95
|
+
const issuedAt = Math.floor(Number(now) / 1000);
|
|
96
|
+
const bodyHash = createHash("sha256").update(String(body)).digest("hex");
|
|
97
|
+
const transcript = adminAuthTranscript({ origin, method: String(method).toUpperCase(), pathname, bodyHash, issuedAt, nonce });
|
|
98
|
+
const signature = createHmac("sha256", secret).update(transcript).digest("base64url");
|
|
99
|
+
return {
|
|
100
|
+
"X-Bridge-Admin-Scheme": ADMIN_AUTH_SCHEME,
|
|
101
|
+
"X-Bridge-Admin-Time": String(issuedAt),
|
|
102
|
+
"X-Bridge-Admin-Nonce": nonce,
|
|
103
|
+
"X-Bridge-Admin-Body-SHA256": bodyHash,
|
|
104
|
+
"X-Bridge-Admin-Signature": signature,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
83
108
|
export function accountRoleNames() {
|
|
84
109
|
return Object.keys(ACCOUNT_ROLES);
|
|
85
110
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OPERATION_APPROVAL_SCOPES,
|
|
3
|
+
approvePendingOperation,
|
|
4
|
+
clearOperationLeases,
|
|
5
|
+
grantOperationLease,
|
|
6
|
+
listOperationApprovals,
|
|
7
|
+
revokeOperationLease,
|
|
8
|
+
} from "./operation-authorization.mjs";
|
|
9
|
+
import { loadState } from "./state.mjs";
|
|
10
|
+
|
|
11
|
+
const ACTIONS = new Set(["list", "approve", "grant", "revoke", "clear"]);
|
|
12
|
+
|
|
13
|
+
export function createApprovalCommand({ chooseWorkspace, confirm }) {
|
|
14
|
+
if (typeof chooseWorkspace !== "function" || typeof confirm !== "function") {
|
|
15
|
+
throw new TypeError("approval command requires chooseWorkspace and confirm dependencies");
|
|
16
|
+
}
|
|
17
|
+
return async function approvalCommand(args) {
|
|
18
|
+
const action = String(args._[0] || "list").toLowerCase();
|
|
19
|
+
if (!ACTIONS.has(action)) throw new Error(`Unknown approval action: ${action}`);
|
|
20
|
+
const workspace = await chooseWorkspace({ ...args, _: [] }, { promptOnFirstRun: false, save: false, allowPositional: false });
|
|
21
|
+
const state = loadState(workspace, { stateDir: args.stateDir });
|
|
22
|
+
const root = state.paths.profileDir;
|
|
23
|
+
if (action === "list") return renderList(listOperationApprovals(root), args.json === true);
|
|
24
|
+
if (action === "approve") {
|
|
25
|
+
const id = requiredPositional(args, 1, "approval approve requires APPROVAL_ID");
|
|
26
|
+
return renderLease(await approvePendingOperation(
|
|
27
|
+
root,
|
|
28
|
+
id,
|
|
29
|
+
args.duration || (args.full ? "8h" : "1h"),
|
|
30
|
+
Date.now(),
|
|
31
|
+
args.full ? "full" : "",
|
|
32
|
+
), args.json === true, "Approved");
|
|
33
|
+
}
|
|
34
|
+
if (action === "grant") {
|
|
35
|
+
const scope = requiredPositional(args, 1, "approval grant requires SCOPE");
|
|
36
|
+
if (!OPERATION_APPROVAL_SCOPES.includes(scope)) {
|
|
37
|
+
throw new Error(`approval scope must be one of: ${OPERATION_APPROVAL_SCOPES.join(", ")}`);
|
|
38
|
+
}
|
|
39
|
+
const clientId = String(args.client || "");
|
|
40
|
+
const accountId = String(args.account || "");
|
|
41
|
+
if (!clientId) throw new Error("approval grant requires --client CLIENT_ID; use * only for an intentional all-client lease");
|
|
42
|
+
if (!accountId) throw new Error("approval grant requires --account ACCOUNT_ID; use * only for an intentional all-account lease");
|
|
43
|
+
return renderLease(await grantOperationLease(root, {
|
|
44
|
+
accountId,
|
|
45
|
+
clientId,
|
|
46
|
+
scope,
|
|
47
|
+
duration: args.duration || "1h",
|
|
48
|
+
}), args.json === true, "Granted");
|
|
49
|
+
}
|
|
50
|
+
if (action === "revoke") {
|
|
51
|
+
const id = requiredPositional(args, 1, "approval revoke requires LEASE_ID");
|
|
52
|
+
const removed = await revokeOperationLease(root, id);
|
|
53
|
+
if (args.json) console.log(JSON.stringify({ lease_id: id, revoked: removed }, null, 2));
|
|
54
|
+
else console.log(removed ? `Revoked capability lease: ${id}` : `Capability lease was not active: ${id}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const approved = await confirm("Revoke all active remote capability leases?", args.yes === true);
|
|
58
|
+
if (!approved) {
|
|
59
|
+
console.log("No capability leases were changed.");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await clearOperationLeases(root);
|
|
63
|
+
if (args.json) console.log(JSON.stringify({ cleared: true }, null, 2));
|
|
64
|
+
else console.log("Revoked all active remote capability leases.");
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderList(result, json) {
|
|
69
|
+
if (json) {
|
|
70
|
+
console.log(JSON.stringify(result, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!result.pending.length && !result.leases.length) {
|
|
74
|
+
console.log("No pending approvals or active capability leases.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (result.pending.length) {
|
|
78
|
+
console.log("Pending approvals:");
|
|
79
|
+
for (const item of result.pending) {
|
|
80
|
+
console.log(` ${item.id}\t${scopeText(item.scopes)}\t${item.category}\tclient ${shortId(item.client_id)}\texpires ${formatTime(item.expires_at)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (result.leases.length) {
|
|
84
|
+
console.log("Active capability leases:");
|
|
85
|
+
for (const item of result.leases) {
|
|
86
|
+
console.log(` ${item.id}\t${scopeText(item.scopes)}\tclient ${shortId(item.client_id)}\texpires ${formatTime(item.expires_at)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderLease(lease, json, verb) {
|
|
92
|
+
if (json) {
|
|
93
|
+
console.log(JSON.stringify(lease, null, 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log(`${verb} ${scopeText(lease.scopes)} capability lease for client ${shortId(lease.client_id)} until ${formatTime(lease.expires_at)}.`);
|
|
97
|
+
console.log(`Lease ID: ${lease.id}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function requiredPositional(args, index, message) {
|
|
101
|
+
const value = String(args._[index] || "");
|
|
102
|
+
if (!value) throw new Error(message);
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function scopeText(scopes) {
|
|
107
|
+
return Array.isArray(scopes) ? scopes.join("+") : "<invalid-scope>";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function shortId(value) {
|
|
111
|
+
const text = String(value || "");
|
|
112
|
+
return text === "*" ? "*" : `${text.slice(0, 14)}...${text.slice(-6)}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function formatTime(epochSeconds) {
|
|
116
|
+
return new Date(Number(epochSeconds) * 1000).toISOString();
|
|
117
|
+
}
|
|
@@ -3,10 +3,10 @@ import { normalizeLogLevel } from "./log.mjs";
|
|
|
3
3
|
const BOOLEAN_OPTIONS = new Set([
|
|
4
4
|
"help", "version", "quiet", "json", "verbose", "rotateSecrets", "forceWorker",
|
|
5
5
|
"daemonOnly", "noAutostart", "noWrite", "noExec", "fullEnv", "unrestrictedPaths", "absolutePaths",
|
|
6
|
-
"yes", "keepWorker", "allowInsecurePermissions", "showPaths",
|
|
6
|
+
"yes", "keepWorker", "allowInsecurePermissions", "showPaths", "full",
|
|
7
7
|
]);
|
|
8
8
|
const VALUE_OPTIONS = new Set([
|
|
9
|
-
"workspace", "stateDir", "workerName", "profile", "execMode", "client", "logLevel", "logFormat",
|
|
9
|
+
"workspace", "stateDir", "workerName", "profile", "execMode", "client", "account", "duration", "logLevel", "logFormat",
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
12
|
const LOG_FORMATS = new Set(["text", "json"]);
|
|
@@ -28,6 +28,7 @@ const COMMAND_OPTIONS = new Map(Object.entries({
|
|
|
28
28
|
autostart: new Set(["workspace", "stateDir", "quiet"]),
|
|
29
29
|
resource: new Set(["workspace", "stateDir", "allowInsecurePermissions", "showPaths", "json"]),
|
|
30
30
|
account: new Set(["workspace", "stateDir", "json", "yes"]),
|
|
31
|
+
approval: new Set(["workspace", "stateDir", "json", "yes", "client", "account", "duration", "full"]),
|
|
31
32
|
browser: new Set(["workspace", "stateDir", "json"]),
|
|
32
33
|
job: new Set(["workspace", "stateDir", "json", "yes"]),
|
|
33
34
|
uninstall: new Set(["stateDir", "keepWorker", "yes"]),
|
|
@@ -41,6 +42,7 @@ const STATIC_POSITIONAL_RULES = new Map([
|
|
|
41
42
|
const RESOURCE_POSITIONAL_LIMITS = new Map(Object.entries({ add: 3, "generate-ssh-key": 3, remove: 2, check: 2 }));
|
|
42
43
|
const JOB_POSITIONAL_LIMITS = new Map(Object.entries({ read: 2, inspect: 2, cancel: 2, approve: 2, submit: 2 }));
|
|
43
44
|
const ACCOUNT_POSITIONAL_LIMITS = new Map(Object.entries({ list: 1, add: 3, role: 3, enable: 2, disable: 2, "rotate-password": 2, remove: 2 }));
|
|
45
|
+
const APPROVAL_POSITIONAL_LIMITS = new Map(Object.entries({ list: 1, approve: 2, grant: 2, revoke: 2, clear: 1 }));
|
|
44
46
|
const ACTION_POSITIONAL_RULES = new Map(Object.entries({
|
|
45
47
|
workspace(args) {
|
|
46
48
|
const action = String(args._[0] || "show");
|
|
@@ -59,6 +61,10 @@ const ACTION_POSITIONAL_RULES = new Map(Object.entries({
|
|
|
59
61
|
const action = String(args._[0] || "list");
|
|
60
62
|
return { max: ACCOUNT_POSITIONAL_LIMITS.get(action) ?? 1, tooMany: `account ${action} received too many positional arguments` };
|
|
61
63
|
},
|
|
64
|
+
approval(args) {
|
|
65
|
+
const action = String(args._[0] || "list");
|
|
66
|
+
return { max: APPROVAL_POSITIONAL_LIMITS.get(action) ?? 1, tooMany: `approval ${action} received too many positional arguments` };
|
|
67
|
+
},
|
|
62
68
|
browser(args) {
|
|
63
69
|
const action = String(args._[0] || "status");
|
|
64
70
|
return { max: 1, tooMany: `browser ${action} received too many positional arguments` };
|
package/src/local/cli.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { assertCanonicalFullPolicy, POLICY_PROFILES, toolsForPolicy } from "./to
|
|
|
10
10
|
import { resolvePolicy } from "./cli-policy.mjs";
|
|
11
11
|
import { effectiveLogFormat, effectiveLogLevel, normalizeCommand, parseArgs, validateCommandOptions, validateLoggingOptions, validatePositionals } from "./cli-options.mjs";
|
|
12
12
|
import { createLocalAdminCommands } from "./cli-local-admin.mjs";
|
|
13
|
+
import { createApprovalCommand } from "./cli-approval.mjs";
|
|
13
14
|
import { createServiceCommand } from "./cli-service.mjs";
|
|
14
15
|
import { generateAccountPassword } from "./account-admin.mjs";
|
|
15
16
|
import { accountAdminClient, createAccountCommand } from "./cli-account-admin.mjs";
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
|
|
50
51
|
const localAdminCommands = createLocalAdminCommands({ chooseWorkspace, confirm });
|
|
51
52
|
const accountCommand = createAccountCommand({ chooseWorkspace, confirm });
|
|
53
|
+
const approvalCommand = createApprovalCommand({ chooseWorkspace, confirm });
|
|
52
54
|
const serviceCommand = createServiceCommand({ chooseWorkspace, stateRootFromArgs, structuredLogger });
|
|
53
55
|
|
|
54
56
|
const COMMAND_HANDLERS = new Map([
|
|
@@ -64,6 +66,7 @@ const COMMAND_HANDLERS = new Map([
|
|
|
64
66
|
["rotate-secrets", rotateSecretsCommand],
|
|
65
67
|
["resource", localAdminCommands.resourceCommand],
|
|
66
68
|
["account", accountCommand],
|
|
69
|
+
["approval", approvalCommand],
|
|
67
70
|
["browser", localAdminCommands.browserCommand],
|
|
68
71
|
["job", localAdminCommands.jobCommand],
|
|
69
72
|
["uninstall", uninstallCommand],
|
|
@@ -300,12 +303,13 @@ async function ensureInitialOwnerAccount(state) {
|
|
|
300
303
|
function createRemoteRuntime({ args, workspace, state, daemonLock }) {
|
|
301
304
|
return new LocalRuntime({
|
|
302
305
|
workerUrl: state.worker.url,
|
|
303
|
-
|
|
306
|
+
deviceIdentity: state.worker.deviceIdentity,
|
|
304
307
|
expectedRelayVersion: currentPackageVersion(),
|
|
305
308
|
workspace,
|
|
306
309
|
policy: state.policy,
|
|
307
310
|
logger: createLogger({ level: args.json ? "error" : effectiveLogLevel(args), format: effectiveLogFormat(args), component: "daemon" }),
|
|
308
311
|
jobRoot: join(state.paths.profileDir, "jobs"),
|
|
312
|
+
approvalRoot: state.paths.profileDir,
|
|
309
313
|
resources: state.resources,
|
|
310
314
|
resourceStatePath: state.paths.statePath,
|
|
311
315
|
browserStateRoot: state.paths.stateRoot,
|
|
@@ -745,9 +749,16 @@ Commands:
|
|
|
745
749
|
status Print redacted local profile state and Worker health
|
|
746
750
|
doctor Check Node, Wrangler, Cloudflare login, Worker health
|
|
747
751
|
full-test Run real local full-profile capability tests in a temporary sandbox
|
|
748
|
-
rotate-secrets Rotate account-admin,
|
|
752
|
+
rotate-secrets Rotate account-admin, device identity, and global token-version secrets
|
|
749
753
|
account list|add|role|enable|disable|rotate-password|remove
|
|
750
754
|
Manage isolated remote accounts and targeted revocation
|
|
755
|
+
approval list Show pending high-impact operations and active capability leases
|
|
756
|
+
approval approve APPROVAL_ID [--duration 1h] [--full]
|
|
757
|
+
Approve its scope, or open an explicit full window (maximum 8h)
|
|
758
|
+
approval grant SCOPE --account ACCOUNT_ID --client CLIENT_ID [--duration 1h]
|
|
759
|
+
Pre-authorize a bounded scope; use * values only intentionally
|
|
760
|
+
approval revoke LEASE_ID | approval clear
|
|
761
|
+
Revoke one or all local capability leases
|
|
751
762
|
resource generate-ssh-key NAME [PATH]
|
|
752
763
|
Generate/reuse an Ed25519 key locally and register its private file by alias
|
|
753
764
|
browser status Show browser-extension bridge and connection status
|