@ours.network/fleet 1.2.0-nightly.43 → 1.2.0-nightly.44

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.
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.2.0-nightly.43",
3
- "buildId": "db00c6f98ef1",
4
- "commit": "8116950e551f2c3e1d9004bf9c138d2659a93266",
2
+ "version": "1.2.0-nightly.44",
3
+ "buildId": "0814e82b7dde",
4
+ "commit": "94ad261abac133a3c17081c1a531a8a363178470",
5
5
  "dirty": true,
6
- "builtAt": "2026-10-04T05:55:03.625Z",
6
+ "builtAt": "2026-10-04T08:52:19.101Z",
7
7
  "capabilities": [
8
8
  "workspace.enroll.preserve-profile-v1",
9
9
  "cowork.http-management-v1",
@@ -567,7 +567,13 @@ export async function controlRequest(stateDir, request, timeoutMs = 120_000) {
567
567
  socket.once('error', error => {
568
568
  clearTimeout(timer);
569
569
  const code = error.code;
570
- reject(new SessionControlError(code === 'ENOENT' || code === 'ECONNREFUSED' ? 'control-unavailable' : 'backend', `role control socket: ${error.message}`));
570
+ const permissionHint = code === 'EPERM' || code === 'EACCES'
571
+ ? `; the operating system denied access before '${request.command}' reached the supervisor. `
572
+ + 'Check socket permissions and the command execution sandbox. '
573
+ + 'An existing socket does not prove this process can connect; do not bypass Fleet audit. '
574
+ + 'For managed workspace sessions, see docs/validation/managed-cli-permissions.md.'
575
+ : '';
576
+ reject(new SessionControlError(code === 'ENOENT' || code === 'ECONNREFUSED' ? 'control-unavailable' : 'backend', `role control socket: ${error.message}${permissionHint}`));
571
577
  });
572
578
  socket.on('data', chunk => {
573
579
  buffer += chunk;
@@ -0,0 +1,197 @@
1
+ # Managed Fleet CLI from Codex and Claude Code workspace sandboxes
2
+
3
+ A managed Fleet CLI invocation sends `fleet_audit_begin` to the supervisor's Unix
4
+ socket before parsing commands, including `--help`. A socket that exists and is
5
+ owned by the current user can still return `connect EPERM` from a restricted
6
+ command sandbox. This is not evidence that the supervisor is down or Fleet needs
7
+ reinstallation. Check filesystem permissions too: `EACCES`/`EPERM` alone does not
8
+ identify which policy denied access.
9
+
10
+ ## Use native execution rules, without a new transport
11
+
12
+ Keep the role's `approval: auto`, `filesystem: workspace`, and `unattended: deny`.
13
+ For an operator authorized to manage rooms, explicitly approve the necessary CLI
14
+ operations using Codex's standard execution rules. A matching operation executes
15
+ outside Codex's command sandbox, retaining the normal CLI, supervisor audit,
16
+ configuration, credentials, and confirmation checks. Other shell commands remain
17
+ sandboxed. This does not override an outer container or host sandbox.
18
+
19
+ In the **trusted workspace** create `.codex/rules/ours-fleet.rules`, substituting
20
+ absolute paths to the installed Node binary, installed Fleet CLI, and approved
21
+ configuration. Example for an operator allowed to delete rooms in this Fleet:
22
+
23
+ ```python
24
+ prefix_rule(
25
+ pattern=["/opt/node/bin/node", "/opt/fleet/dist/cli.js", "room", "delete",
26
+ "--configuration", "/srv/fleet/fleet.yaml", "--json", "--"],
27
+ decision="allow",
28
+ )
29
+ ```
30
+
31
+ Invoke exactly that prefix, followed by the room ID and the confirmation ID:
32
+
33
+ ```sh
34
+ /opt/node/bin/node /opt/fleet/dist/cli.js room delete --configuration /srv/fleet/fleet.yaml --json -- ROOM_ID ROOM_ID
35
+ ```
36
+
37
+ To authorize only one room, append both concrete IDs to the rule's pattern.
38
+ The `--` terminator prevents trailing arguments from changing the fixed options.
39
+ Do not approve the entire Node executable or the entire Fleet CLI. Do not add
40
+ `spawn`, `ours call`, arbitrary scripts, or configuration overrides to this rule.
41
+ A separate fixed `[NODE, CLI, "--help"]` rule can authorize managed help.
42
+
43
+ Restart the Codex session after changing rules. Native Fleet and bundled Codex ACP
44
+ use the same workspace rules. Codex must trust that workspace; configure trust
45
+ through the normal operator workflow rather than enabling it from an agent tool.
46
+ No runtime services or installed packages need restarting for this policy change.
47
+
48
+ ## Boundaries
49
+
50
+ - A rule grants the named operation real authority. The repeated room ID is CLI
51
+ confirmation, not a substitute for user authorization.
52
+ - Rules apply to trusted Codex sessions in this workspace, not exclusively to one
53
+ Fleet identity. They depend on the launcher's inherited environment. Keep the
54
+ executable, CLI installation, credentials, and configuration outside the
55
+ agent-writable workspace. Do not treat this as an identity isolation feature.
56
+ - User/organization denied-read rules or an outer sandbox may forbid execution
57
+ outside the command sandbox. These restrictions take precedence; do not remove
58
+ them implicitly to make Fleet work.
59
+ - On tested Codex 0.159.0, quoting the executable/script tokens prevented the rule
60
+ from granting execution outside the sandbox. Use the canonical simple-token
61
+ invocation above. Paths needing shell quoting have not been qualified.
62
+ - Merely enabling network inside the Linux command sandbox was insufficient:
63
+ its user namespace maps ancestor directory owners to UID 65534, which the SDK's
64
+ strict credential ancestry checks reject. Approved CLI execution avoids that
65
+ namespace; SDK security checks remain unchanged.
66
+
67
+ ## Reproduction and regression checks
68
+
69
+ The [issue author’s validation matrix](https://github.com/adapt-toolkit/ours-fleet/issues/231)
70
+ used Codex 0.159.0 on macOS/Linux, native and ACP. Those results are not new runs
71
+ on every revision of this fixture. Build Fleet and supply the complete installed
72
+ Codex distribution (including its adjacent helper binaries) and a built Cowork CLI.
73
+ Run from the Fleet repo:
74
+
75
+ ```sh
76
+ FLEET_CODEX_BIN=/absolute/codex \
77
+ FLEET_COWORK_CLI=/absolute/cowork/dist/cli.js \
78
+ FLEET_TEST_SESSION=codex-app-server \
79
+ FLEET_TEST_TMP_PREFIX=/absolute/private-test-parent/fleet-ipc- \
80
+ node test/fleet-cli-permissions.integration.mjs
81
+ ```
82
+
83
+ Repeat with `FLEET_TEST_SESSION=acp`. The test parent must be writable, outside
84
+ `/tmp` and outside the tested workspace: ACP may allow `/tmp` independently.
85
+ Omit `FLEET_TEST_SESSION` to test standalone `codex exec`.
86
+
87
+ The local scripted Responses provider supplies only command requests. Real Codex,
88
+ Fleet adapters, CLI, control socket, audit store, daemon, and Cowork perform the
89
+ operations. No external model or production identities/rooms are used. Child processes are stopped and temporary state is removed afterward. To retain
90
+ fixture evidence, set `FLEET_TEST_KEEP_ARTIFACTS=1`. The fixture allowlists its
91
+ launcher environment and uses a disposable HOME; no provider keys, preloads, or
92
+ user configuration are inherited.
93
+
94
+ Checks: baseline network-denied `--help`; permitted help; wrong confirmation
95
+ leaves the room intact; correct confirmation deletes the actual empty test room
96
+ from Fleet and Cowork; caller audit remains present; unrelated outside-workspace
97
+ write, `NODE_OPTIONS` injection, and a different configuration remain denied.
98
+ The fixture uses `OURS_FLEET_SUPERVISOR=none`: launchctl/systemd workers and deletion
99
+ of a live room containing agents are outside this test's coverage.
100
+
101
+ Reference: [Codex execution rules](https://learn.chatgpt.com/docs/agent-configuration/rules).
102
+
103
+ ## Claude Code: the same CLI path, different permission settings
104
+
105
+ The issue author verified Claude Code 2.1.284 on macOS and Linux, both standalone and through
106
+ Fleet's bundled Claude ACP adapter (0.63.0). Fleet `approval: auto` maps to
107
+ `acceptEdits`, which is not blanket permission for Bash. Fleet's `filesystem:
108
+ workspace` also does not by itself enable Claude's OS sandbox. Explicitly enable
109
+ it and configure both the execution exclusion and the command permission:
110
+
111
+ ```json
112
+ {
113
+ "sandbox": {
114
+ "enabled": true,
115
+ "failIfUnavailable": true,
116
+ "allowUnsandboxedCommands": false,
117
+ "excludedCommands": [
118
+ "/opt/node/bin/node /opt/fleet/dist/cli.js room delete --configuration /srv/fleet/fleet.yaml --json -- *"
119
+ ]
120
+ },
121
+ "permissions": {
122
+ "allow": [
123
+ "Bash(/opt/node/bin/node /opt/fleet/dist/cli.js room delete --configuration /srv/fleet/fleet.yaml --json -- *)"
124
+ ]
125
+ }
126
+ }
127
+ ```
128
+
129
+ Use operator-controlled Claude settings. The fixture uses an isolated settings
130
+ file delivered with `--settings` (standalone) or Fleet's supported settings overlay
131
+ metadata (bundled ACP). It does not modify the user's settings. As with the Codex
132
+ rule, append exact room IDs instead of `*` to restrict this to one room.
133
+
134
+ `excludedCommands` controls placement outside the command sandbox;
135
+ `permissions.allow` authorizes the action. Neither should be replaced with global
136
+ `bypassPermissions`, blanket `Bash(*)`, or an exception for all Node programs.
137
+ An external sandbox or managed policy can still override this configuration.
138
+
139
+ Linux inside an unprivileged Docker container additionally needed
140
+ `sandbox.enableWeakerNestedSandbox: true`: otherwise bubblewrap failed to mount
141
+ `/proc` before any CLI command ran. This documented setting bind-mounts the
142
+ container's existing `/proc`, weakening that part of the nested isolation. It is
143
+ specific to the nested test environment, not an unconditional Linux requirement.
144
+ The tests still prove Unix-socket denial and filesystem write denial. The outer
145
+ Docker seccomp profile was unconfined to support nesting, as in the Codex tests.
146
+
147
+ Run the same integration scenario with:
148
+
149
+ ```sh
150
+ FLEET_CLAUDE_BIN=/absolute/claude \
151
+ FLEET_COWORK_CLI=/absolute/cowork/dist/cli.js \
152
+ FLEET_TEST_TMP_PREFIX=/absolute/private-test-parent/fleet-claude- \
153
+ FLEET_TEST_SESSION=claude-acp \
154
+ node test/fleet-cli-permissions.integration.mjs
155
+ ```
156
+
157
+ Omit `FLEET_TEST_SESSION` for standalone Claude. In the nested Linux fixture add
158
+ `FLEET_TEST_NESTED_SANDBOX=1` explicitly. A scripted local Anthropic Messages/SSE
159
+ provider requests the real Bash call and captures the actual tool result. The
160
+ negative baseline grants Bash permission without an exclusion and verifies that
161
+ the supervisor socket is still blocked. The write probe is separately authorized
162
+ inside the sandbox; it must execute and report an OS denial, not merely fail an
163
+ approval prompt. No real model/account credentials are used.
164
+
165
+ All other limits above apply: empty room, isolated audit/control server,
166
+ `supervisor=none` workers, and adapter coverage rather than full production runner.
167
+
168
+ Reference: [Claude sandbox exclusions and permissions](https://code.claude.com/docs/en/sandboxing#run-commands-outside-the-sandbox-with-excludedcommands).
169
+
170
+
171
+ ## Port validation for issue #231
172
+
173
+ The separate upstream port was rerun on Linux amd64 in a disposable Node 22.23.1
174
+ container with Codex 0.160.0 and Claude Code 2.1.289:
175
+
176
+ | Harness execution | Result |
177
+ | --- | --- |
178
+ | Codex native app-server | PASS |
179
+ | Bundled Codex ACP | PASS |
180
+ | Claude standalone | PASS |
181
+ | Bundled Claude ACP | PASS |
182
+
183
+ Each complete run checks actual socket denial, permitted help, wrong-confirmation
184
+ refusal, deletion of its own empty Fleet/Cowork room, preserved caller audit, and
185
+ OS denial of an unrelated write, preload injection, and alternate configuration.
186
+ The scripted providers and all state are local; external network access is disabled.
187
+ For these nested tests only, the outer Docker seccomp and AppArmor profiles were
188
+ unconfined and Claude used `enableWeakerNestedSandbox: true`. No host policies or
189
+ production services were changed. The inner command sandbox remains enforced by
190
+ the negative checks. These are container results, not a qualification of normal
191
+ host settings. Initial missing-helper and outer-policy failures were retained
192
+ alongside the passing runs; they are not counted as passing tests.
193
+
194
+ The port also passed 88 diagnostic/conversation/audit unit checks, a build, and an
195
+ npm package dry-run check for this document. The diagnostic preserves the prior
196
+ error kinds and audited CLI failure before parsing. This does not claim a fresh
197
+ macOS run, full production runner coverage, or live-agent room deletion.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.2.0-nightly.43",
3
+ "version": "1.2.0-nightly.44",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -20,6 +20,7 @@
20
20
  "README.md",
21
21
  "docs/task-workspaces.md",
22
22
  "docs/room-layouts.md",
23
+ "docs/validation/managed-cli-permissions.md",
23
24
  "LICENSE"
24
25
  ],
25
26
  "engines": {