chati-dev 4.5.16 → 4.5.27
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/README.md +8 -3
- package/bin/chati.js +124 -66
- package/framework/agents/build/dev.md +5 -5
- package/framework/agents/deploy/devops.md +7 -7
- package/framework/agents/discover/brief.md +4 -4
- package/framework/agents/discover/brownfield-wu.md +3 -3
- package/framework/agents/discover/greenfield-wu.md +3 -3
- package/framework/agents/plan/architect.md +2 -2
- package/framework/agents/plan/detail.md +4 -4
- package/framework/agents/plan/phases.md +2 -2
- package/framework/agents/plan/tasks.md +2 -2
- package/framework/agents/plan/ux.md +2 -2
- package/framework/agents/quality/qa-implementation.md +11 -10
- package/framework/agents/quality/qa-planning.md +3 -3
- package/framework/agents/quality/qa-visual.md +1 -1
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +18 -18
- package/framework/context/protocols.md +1 -1
- package/framework/context/quality.md +1 -1
- package/framework/context/root.md +4 -4
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/orchestrator.yaml +2 -2
- package/framework/domains/constitution.yaml +1 -1
- package/framework/hooks/advance-trigger.js +4 -6
- package/framework/hooks/git-push-authority.js +45 -37
- package/framework/hooks/mode-governance.js +149 -40
- package/framework/hooks/model-governance.js +13 -20
- package/framework/hooks/prism-engine.js +74 -92
- package/framework/hooks/reasoning-escalator.js +23 -40
- package/framework/hooks/session-digest.js +12 -13
- package/framework/hooks/session-reader.js +224 -0
- package/framework/hooks/session-writer.js +195 -0
- package/framework/hooks/team-quality-gate.js +34 -24
- package/framework/i18n/en.yaml +2 -2
- package/framework/i18n/es.yaml +2 -2
- package/framework/i18n/fr.yaml +2 -2
- package/framework/i18n/pt.yaml +2 -2
- package/framework/intelligence/context-engine.md +4 -4
- package/framework/intelligence/memory-layer.md +1 -1
- package/framework/manifest.json +129 -119
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +252 -25
- package/framework/orchestrator/chati.md +238 -70
- package/framework/schemas/session.schema.json +1 -1
- package/framework/tasks/orchestrator-deviation.md +1 -1
- package/framework/tasks/orchestrator-escalate.md +1 -1
- package/framework/tasks/orchestrator-handoff.md +6 -6
- package/framework/tasks/orchestrator-health.md +5 -9
- package/framework/tasks/orchestrator-mode-switch.md +3 -7
- package/framework/tasks/orchestrator-resume.md +10 -14
- package/framework/tasks/orchestrator-route.md +3 -3
- package/framework/tasks/orchestrator-spawn-terminal.md +1 -1
- package/framework/tasks/orchestrator-status.md +9 -9
- package/framework/tasks/orchestrator-suggest-mode.md +1 -1
- package/framework/tasks/qa-impl-consolidate.md +2 -2
- package/framework/tasks/qa-impl-performance-test.md +4 -4
- package/framework/tasks/qa-impl-regression-check.md +4 -4
- package/framework/tasks/qa-impl-sast-scan.md +1 -1
- package/framework/tasks/qa-impl-test-execute.md +1 -1
- package/framework/tasks/qa-impl-verdict.md +2 -2
- package/framework/tasks/qa-planning-consolidate.md +3 -3
- package/framework/tasks/qa-planning-coverage-plan.md +2 -2
- package/framework/tasks/qa-planning-gate-define.md +4 -4
- package/framework/tasks/qa-planning-risk-matrix.md +4 -4
- package/framework/tasks/qa-planning-test-strategy.md +2 -2
- package/node_modules/@chati/browser-capability/src/index.js +12 -2
- package/node_modules/@chati/planning/src/index.js +24 -4
- package/node_modules/@chati/provider-registry/src/index.js +11 -0
- package/node_modules/@chati/rail/src/index.js +1967 -83
- package/node_modules/@chati/release-lane/README.md +12 -8
- package/node_modules/@chati/release-lane/package.json +1 -1
- package/node_modules/@chati/release-lane/src/index.js +1426 -58
- package/node_modules/@chati/review-council/src/index.js +63 -0
- package/node_modules/@chati/tracking-clickup/README.md +13 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +690 -30
- package/package-artifact-manifest.json +1 -0
- package/package-artifact-manifest.sig +1 -0
- package/package.json +16 -7
- package/scripts/verify-real-harness-e2e.js +1581 -0
- package/src/config/framework-adapter.js +4 -4
- package/src/installer/core.js +148 -53
- package/src/installer/manifest.js +140 -9
- package/src/installer/package-artifact.js +249 -0
- package/src/installer/templates.js +65 -20
- package/src/installer-v2/catalog-client.js +531 -23
- package/src/installer-v2/index.js +91 -34
- package/src/installer-v2/installation-authority.js +327 -0
- package/src/installer-v2/provider-executable.js +235 -0
- package/src/installer-v2/wizard-installation.js +1 -1
- package/src/orchestrator/browser-runtime.js +44 -13
- package/src/orchestrator/cli.js +1545 -174
- package/src/orchestrator/clickup-projection.js +43 -1
- package/src/orchestrator/clickup-runtime.js +355 -39
- package/src/orchestrator/doctor.js +16 -3
- package/src/orchestrator/index.js +16 -0
- package/src/orchestrator/planning-runtime.js +20 -2
- package/src/orchestrator/rail-adjudication-evidence.js +234 -0
- package/src/orchestrator/rail-evidence-authority.js +147 -0
- package/src/orchestrator/rail-execution-evidence.js +45 -0
- package/src/orchestrator/rail-runtime.js +836 -56
- package/src/orchestrator/release-runtime.js +65 -6
- package/src/orchestrator/review-runtime.js +186 -41
- package/src/orchestrator/runtime-installation-v2.js +236 -20
- package/src/orchestrator/session-manager.js +1167 -53
- package/src/terminal/adapters/claude-adapter.js +3 -1
- package/src/terminal/adapters/codex-adapter.js +2 -0
- package/src/terminal/adapters/grok-adapter.js +7 -4
- package/src/terminal/handoff-parser.js +19 -1
- package/src/terminal/prompt-builder.js +9 -1
- package/src/terminal/provider-preflight.js +36 -3
- package/src/terminal/rail-execution-worktree.js +213 -0
- package/src/terminal/rail-prompts.js +169 -0
- package/src/terminal/rail-readonly-workspace.js +324 -0
- package/src/terminal/run-agent.js +384 -24
- package/src/terminal/run-parallel.js +5 -0
- package/src/terminal/run-rail-adjudication.js +323 -0
- package/src/terminal/run-rail-review.js +291 -0
- package/src/terminal/run-rail-rework.js +325 -0
- package/src/terminal/run-rail-task.js +380 -0
- package/src/terminal/run-team.js +5 -0
- package/src/terminal/spawner.js +1225 -77
- package/src/wizard/index.js +3 -2
|
@@ -0,0 +1,1581 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
5
|
+
import {
|
|
6
|
+
chmodSync, closeSync, constants, existsSync, fchmodSync, fstatSync, mkdirSync, mkdtempSync, openSync,
|
|
7
|
+
readSync, readdirSync, realpathSync, rmSync, writeFileSync, writeSync,
|
|
8
|
+
} from 'node:fs';
|
|
9
|
+
import { homedir, tmpdir } from 'node:os';
|
|
10
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import * as yaml from 'js-yaml';
|
|
13
|
+
import {
|
|
14
|
+
loadRuntimeInstallationV2, resolveRuntimeRailInvocationV2,
|
|
15
|
+
} from '../src/orchestrator/runtime-installation-v2.js';
|
|
16
|
+
import {
|
|
17
|
+
digestProviderExecutable, openPinnedProviderExecutable, openPinnedProviderSupportingExecutables,
|
|
18
|
+
} from '../src/installer-v2/provider-executable.js';
|
|
19
|
+
import {
|
|
20
|
+
installationAuthorityPublicKeyPath, installationAuthorityStateDirectory,
|
|
21
|
+
} from '../src/installer-v2/installation-authority.js';
|
|
22
|
+
|
|
23
|
+
const REAL_HARNESS_SEQUENCE = Object.freeze(['claude', 'codex', 'grok']);
|
|
24
|
+
const E2E_ROUTE_REASON = 'installed-harness-e2e-deterministic-contract';
|
|
25
|
+
const PYTHON_PATH = '/usr/bin/python3';
|
|
26
|
+
const BUBBLEWRAP_PATH = '/usr/bin/bwrap';
|
|
27
|
+
const SANDBOX_OUTPUT_PATH = '/output';
|
|
28
|
+
const SANDBOX_STATE_PATH = '/state';
|
|
29
|
+
const SANDBOX_TMP_PATH = '/tmp';
|
|
30
|
+
const SEALED_CODEX_EXECUTABLE_PATH = '/run/chati-sealed/bin/codex';
|
|
31
|
+
const SEALED_CODEX_HOST_PATH = '/run/chati-sealed/bin/codex-code-mode-host';
|
|
32
|
+
const CODEX_RUNTIME_PATHS = Object.freeze(['/usr', '/bin', '/lib', '/lib64', '/etc']);
|
|
33
|
+
const CODEX_DNS_RUNTIME_PATH = '/run/systemd/resolve';
|
|
34
|
+
const GROK_E2E_STRICT_SANDBOX_PROFILE_PREFIX = 'chati-e2e-strict';
|
|
35
|
+
const GROK_SYSTEM_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
|
36
|
+
const GROK_RUNTIME_ENVIRONMENT_KEYS = new Set([
|
|
37
|
+
'HOME', 'USER', 'LOGNAME', 'SHELL', 'TERM', 'COLORTERM', 'LANG', 'LC_ALL', 'LC_CTYPE',
|
|
38
|
+
'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME', 'XDG_RUNTIME_DIR',
|
|
39
|
+
]);
|
|
40
|
+
const GROK_RUNTIME_CREDENTIAL_PREFIXES = Object.freeze(['XAI_', 'GROK_']);
|
|
41
|
+
const SEALED_PROVIDER_RUNNER = String.raw`import fcntl, hashlib, json, os, sys
|
|
42
|
+
F_ADD_SEALS = 1033
|
|
43
|
+
F_SEAL_SEAL = 0x0001
|
|
44
|
+
F_SEAL_SHRINK = 0x0002
|
|
45
|
+
F_SEAL_GROW = 0x0004
|
|
46
|
+
F_SEAL_WRITE = 0x0008
|
|
47
|
+
|
|
48
|
+
def seal_descriptor(source, expected_sha256, label):
|
|
49
|
+
image = os.memfd_create('chati-sealed-provider', os.MFD_ALLOW_SEALING)
|
|
50
|
+
digest = hashlib.sha256()
|
|
51
|
+
os.lseek(source, 0, os.SEEK_SET)
|
|
52
|
+
while True:
|
|
53
|
+
chunk = os.read(source, 1024 * 1024)
|
|
54
|
+
if not chunk:
|
|
55
|
+
break
|
|
56
|
+
digest.update(chunk)
|
|
57
|
+
view = memoryview(chunk)
|
|
58
|
+
while view:
|
|
59
|
+
written = os.write(image, view)
|
|
60
|
+
if written < 1:
|
|
61
|
+
raise RuntimeError('could not write sealed ' + label)
|
|
62
|
+
view = view[written:]
|
|
63
|
+
if digest.hexdigest() != expected_sha256:
|
|
64
|
+
raise RuntimeError('sealed ' + label + ' differs from its signed binding')
|
|
65
|
+
fcntl.fcntl(image, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
|
|
66
|
+
os.lseek(image, 0, os.SEEK_SET)
|
|
67
|
+
return image
|
|
68
|
+
|
|
69
|
+
def read_exact(length, label):
|
|
70
|
+
chunks = []
|
|
71
|
+
remaining = length
|
|
72
|
+
while remaining:
|
|
73
|
+
chunk = os.read(0, remaining)
|
|
74
|
+
if not chunk:
|
|
75
|
+
raise RuntimeError('could not read ' + label)
|
|
76
|
+
chunks.append(chunk)
|
|
77
|
+
remaining -= len(chunk)
|
|
78
|
+
return b''.join(chunks)
|
|
79
|
+
|
|
80
|
+
def seal_bytes(content, label):
|
|
81
|
+
image = os.memfd_create('chati-sealed-' + label, os.MFD_ALLOW_SEALING)
|
|
82
|
+
view = memoryview(content)
|
|
83
|
+
while view:
|
|
84
|
+
written = os.write(image, view)
|
|
85
|
+
if written < 1:
|
|
86
|
+
raise RuntimeError('could not write sealed ' + label)
|
|
87
|
+
view = view[written:]
|
|
88
|
+
fcntl.fcntl(image, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
|
|
89
|
+
os.lseek(image, 0, os.SEEK_SET)
|
|
90
|
+
return image
|
|
91
|
+
|
|
92
|
+
payload = json.loads(sys.argv[1])
|
|
93
|
+
primary = seal_descriptor(3, payload['executable_sha256'], payload['label'])
|
|
94
|
+
os.dup2(primary, 3)
|
|
95
|
+
authority = None
|
|
96
|
+
if payload.get('authority_key_bytes_length', 0):
|
|
97
|
+
authority = seal_bytes(read_exact(payload['authority_key_bytes_length'], 'installation authority public key'), 'authority-key')
|
|
98
|
+
args = [str(authority) if argument == '{authority_fd}' else argument for argument in payload['args']]
|
|
99
|
+
os.execvpe(payload['command'], [payload['command'], *args], os.environ)`;
|
|
100
|
+
const SEALED_CODEX_RUNNER = String.raw`import fcntl, hashlib, json, os, subprocess, sys
|
|
101
|
+
F_ADD_SEALS = 1033
|
|
102
|
+
F_SEAL_SEAL = 0x0001
|
|
103
|
+
F_SEAL_SHRINK = 0x0002
|
|
104
|
+
F_SEAL_GROW = 0x0004
|
|
105
|
+
F_SEAL_WRITE = 0x0008
|
|
106
|
+
|
|
107
|
+
def seal_descriptor(source, expected_sha256, label):
|
|
108
|
+
image = os.memfd_create('chati-sealed-codex', os.MFD_ALLOW_SEALING)
|
|
109
|
+
digest = hashlib.sha256()
|
|
110
|
+
os.lseek(source, 0, os.SEEK_SET)
|
|
111
|
+
while True:
|
|
112
|
+
chunk = os.read(source, 1024 * 1024)
|
|
113
|
+
if not chunk:
|
|
114
|
+
break
|
|
115
|
+
digest.update(chunk)
|
|
116
|
+
view = memoryview(chunk)
|
|
117
|
+
while view:
|
|
118
|
+
written = os.write(image, view)
|
|
119
|
+
if written < 1:
|
|
120
|
+
raise RuntimeError('could not write sealed Codex executable')
|
|
121
|
+
view = view[written:]
|
|
122
|
+
if digest.hexdigest() != expected_sha256:
|
|
123
|
+
raise RuntimeError('sealed ' + label + ' differs from its signed binding')
|
|
124
|
+
fcntl.fcntl(image, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
|
|
125
|
+
os.lseek(image, 0, os.SEEK_SET)
|
|
126
|
+
return image
|
|
127
|
+
|
|
128
|
+
def read_exact(length, label):
|
|
129
|
+
chunks = []
|
|
130
|
+
remaining = length
|
|
131
|
+
while remaining:
|
|
132
|
+
chunk = os.read(0, remaining)
|
|
133
|
+
if not chunk:
|
|
134
|
+
raise RuntimeError('could not read ' + label)
|
|
135
|
+
chunks.append(chunk)
|
|
136
|
+
remaining -= len(chunk)
|
|
137
|
+
return b''.join(chunks)
|
|
138
|
+
|
|
139
|
+
def seal_bytes(content, label):
|
|
140
|
+
image = os.memfd_create('chati-sealed-' + label, os.MFD_ALLOW_SEALING)
|
|
141
|
+
view = memoryview(content)
|
|
142
|
+
while view:
|
|
143
|
+
written = os.write(image, view)
|
|
144
|
+
if written < 1:
|
|
145
|
+
raise RuntimeError('could not write sealed ' + label)
|
|
146
|
+
view = view[written:]
|
|
147
|
+
fcntl.fcntl(image, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
|
|
148
|
+
os.lseek(image, 0, os.SEEK_SET)
|
|
149
|
+
return image
|
|
150
|
+
|
|
151
|
+
payload = json.loads(sys.argv[1])
|
|
152
|
+
primary = seal_descriptor(3, payload['executable_sha256'], 'Codex executable')
|
|
153
|
+
supporting = seal_descriptor(4, payload['supporting_executable_sha256'], 'Codex code-mode host')
|
|
154
|
+
os.dup2(primary, 3)
|
|
155
|
+
os.dup2(supporting, 4)
|
|
156
|
+
credential = read_exact(payload['credential_bytes_length'], 'Codex credential')
|
|
157
|
+
auth = read_exact(payload['auth_bytes_length'], 'Codex auth')
|
|
158
|
+
authority = seal_bytes(read_exact(payload['authority_key_bytes_length'], 'installation authority public key'), 'authority-key')
|
|
159
|
+
auth_fd = seal_bytes(auth, 'auth') if auth else None
|
|
160
|
+
environment = {
|
|
161
|
+
'CODEX_HOME': payload['state_path'],
|
|
162
|
+
'HOME': payload['state_path'],
|
|
163
|
+
'TMPDIR': payload['tmp_path'],
|
|
164
|
+
'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
|
165
|
+
'LANG': 'C.UTF-8',
|
|
166
|
+
'NO_COLOR': '1',
|
|
167
|
+
}
|
|
168
|
+
if payload['credential_name']:
|
|
169
|
+
environment[payload['credential_name']] = credential.decode('utf-8')
|
|
170
|
+
if payload['test_host_pid'] is not None:
|
|
171
|
+
environment['CHATI_TEST_HOST_PID'] = str(payload['test_host_pid'])
|
|
172
|
+
command = [
|
|
173
|
+
payload['bubblewrap_path'], '--die-with-parent', '--unshare-all', '--unshare-user', '--share-net',
|
|
174
|
+
'--disable-userns', '--as-pid-1',
|
|
175
|
+
]
|
|
176
|
+
for runtime_path in payload['runtime_paths']:
|
|
177
|
+
command.extend(['--ro-bind', runtime_path, runtime_path])
|
|
178
|
+
command.extend([
|
|
179
|
+
'--dir', '/run', '--dir', '/run/systemd',
|
|
180
|
+
'--ro-bind', payload['dns_runtime_path'], payload['dns_runtime_path'],
|
|
181
|
+
'--dev', '/dev', '--proc', '/proc', '--tmpfs', payload['tmp_path'],
|
|
182
|
+
'--dir', '/run', '--dir', '/run/chati-sealed', '--dir', '/run/chati-sealed/bin',
|
|
183
|
+
'--perms', '0500', '--ro-bind-data', '3', payload['sealed_executable_path'],
|
|
184
|
+
'--perms', '0500', '--ro-bind-data', '4', payload['sealed_host_path'],
|
|
185
|
+
'--bind-fd', str(payload['project_fd']), payload['project_path'],
|
|
186
|
+
'--ro-bind', '/proc/self/fd/' + str(payload['runtime_fd']), payload['runtime_path'],
|
|
187
|
+
'--bind-fd', str(payload['state_fd']), payload['state_path'],
|
|
188
|
+
])
|
|
189
|
+
if payload['output_fd'] is not None:
|
|
190
|
+
command.extend(['--bind-fd', str(payload['output_fd']), payload['output_path']])
|
|
191
|
+
if auth_fd is not None:
|
|
192
|
+
command.extend(['--perms', '0400', '--ro-bind-data', str(auth_fd), payload['state_path'] + '/auth.json'])
|
|
193
|
+
for directory in payload['authority_key_parent_directories']:
|
|
194
|
+
command.extend(['--dir', directory])
|
|
195
|
+
command.extend([
|
|
196
|
+
'--perms', '0555', '--tmpfs', payload['authority_key_directory'],
|
|
197
|
+
'--perms', '0444', '--file', str(authority), payload['authority_key_destination'],
|
|
198
|
+
'--remount-ro', payload['authority_key_directory'],
|
|
199
|
+
])
|
|
200
|
+
command.extend([
|
|
201
|
+
'--chdir', payload['project_path'],
|
|
202
|
+
payload['sealed_executable_path'], *payload['args'],
|
|
203
|
+
])
|
|
204
|
+
pass_fds = [3, 4, payload['project_fd'], payload['runtime_fd'], payload['state_fd'], authority]
|
|
205
|
+
if payload['output_fd'] is not None:
|
|
206
|
+
pass_fds.append(payload['output_fd'])
|
|
207
|
+
if auth_fd is not None:
|
|
208
|
+
pass_fds.append(auth_fd)
|
|
209
|
+
result = subprocess.run(command, env=environment, pass_fds=tuple(pass_fds), close_fds=True)
|
|
210
|
+
sys.exit(result.returncode)`;
|
|
211
|
+
const CLAUDE_NATIVE_MODELS = Object.freeze({
|
|
212
|
+
'claude-fable-5': 'claude-fable-5',
|
|
213
|
+
'claude-opus-5': 'claude-opus-5',
|
|
214
|
+
'claude-sonnet-5': 'claude-sonnet-5',
|
|
215
|
+
'claude-haiku-4-5': 'claude-haiku-4-5',
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
function fail(message, details = {}) {
|
|
219
|
+
const error = new Error(message);
|
|
220
|
+
error.details = details;
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function sameSnapshot(left, right) {
|
|
225
|
+
return left?.dev === right?.dev
|
|
226
|
+
&& left?.ino === right?.ino
|
|
227
|
+
&& left?.mode === right?.mode
|
|
228
|
+
&& left?.uid === right?.uid
|
|
229
|
+
&& left?.gid === right?.gid
|
|
230
|
+
&& left?.nlink === right?.nlink
|
|
231
|
+
&& left?.size === right?.size
|
|
232
|
+
&& left?.mtimeMs === right?.mtimeMs
|
|
233
|
+
&& left?.ctimeMs === right?.ctimeMs;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function runSecurityTestHook(name, payload) {
|
|
237
|
+
const hook = globalThis[Symbol.for('chati.real-harness-e2e.security-test-hooks')]?.[name];
|
|
238
|
+
if (typeof hook === 'function') hook(payload);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readBoundedRegularFile(path, maxBytes, label) {
|
|
242
|
+
let descriptor;
|
|
243
|
+
try {
|
|
244
|
+
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
|
|
245
|
+
} catch {
|
|
246
|
+
return fail(label + ' is not a bounded regular file');
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
return readBoundedRegularDescriptor(descriptor, maxBytes, label, { path });
|
|
250
|
+
} finally {
|
|
251
|
+
closeSync(descriptor);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function readBoundedRegularDescriptor(descriptor, maxBytes, label, { path = null } = {}) {
|
|
256
|
+
const before = fstatSync(descriptor);
|
|
257
|
+
if (!before.isFile() || before.nlink !== 1 || before.size > maxBytes) {
|
|
258
|
+
return fail(label + ' is not a bounded regular file');
|
|
259
|
+
}
|
|
260
|
+
runSecurityTestHook('afterBoundedFileStat', { path, descriptor, maxBytes, label });
|
|
261
|
+
const bytes = Buffer.allocUnsafe(maxBytes + 1);
|
|
262
|
+
let offset = 0;
|
|
263
|
+
while (offset < bytes.length) {
|
|
264
|
+
const count = readSync(descriptor, bytes, offset, bytes.length - offset, null);
|
|
265
|
+
if (count === 0) break;
|
|
266
|
+
offset += count;
|
|
267
|
+
}
|
|
268
|
+
const after = fstatSync(descriptor);
|
|
269
|
+
if (offset > maxBytes || !after.isFile() || after.nlink !== 1 || !sameSnapshot(before, after)) {
|
|
270
|
+
return fail(label + ' is not a bounded regular file');
|
|
271
|
+
}
|
|
272
|
+
return bytes.subarray(0, offset);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function value(argv, flag, fallback = null) {
|
|
276
|
+
const index = argv.indexOf(flag);
|
|
277
|
+
return index === -1 ? fallback : argv[index + 1];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function parentDirectories(path) {
|
|
281
|
+
const parents = [];
|
|
282
|
+
for (let current = dirname(path); current !== '/'; current = dirname(current)) parents.push(current);
|
|
283
|
+
return parents.reverse();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function resolveE2EAuthorityPublicKeyPath(authorityDirectory) {
|
|
287
|
+
const locations = [
|
|
288
|
+
installationAuthorityPublicKeyPath(authorityDirectory),
|
|
289
|
+
join(installationAuthorityStateDirectory(authorityDirectory), 'installation-ed25519-public.pem'),
|
|
290
|
+
];
|
|
291
|
+
for (const path of locations) {
|
|
292
|
+
try {
|
|
293
|
+
readBoundedRegularFile(path, 64 * 1024, 'installation authority public key');
|
|
294
|
+
return path;
|
|
295
|
+
} catch {
|
|
296
|
+
// Try the legacy location only when the public-only projection is absent.
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return fail('real harness E2E requires the installation authority public key');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function configureGrokStrictSandbox(projectRoot, authorityPublicKey) {
|
|
303
|
+
if (typeof authorityPublicKey !== 'string' || resolve(authorityPublicKey) !== authorityPublicKey) {
|
|
304
|
+
fail('real Grok E2E requires a resolved installation authority public key');
|
|
305
|
+
}
|
|
306
|
+
const sandboxDirectory = join(projectRoot, '.grok');
|
|
307
|
+
const sandboxConfig = join(sandboxDirectory, 'sandbox.toml');
|
|
308
|
+
if (existsSync(sandboxConfig)) {
|
|
309
|
+
fail('real Grok E2E refuses to replace an existing sandbox profile');
|
|
310
|
+
}
|
|
311
|
+
mkdirSync(sandboxDirectory, { recursive: true, mode: 0o700 });
|
|
312
|
+
// A global Grok profile wins a same-name conflict. A fresh opaque name makes
|
|
313
|
+
// a pre-existing permissive user profile unable to shadow this E2E profile.
|
|
314
|
+
const profile = `${GROK_E2E_STRICT_SANDBOX_PROFILE_PREFIX}-${randomBytes(16).toString('hex')}`;
|
|
315
|
+
writeFileSync(sandboxConfig, [
|
|
316
|
+
`[profiles.${profile}]`,
|
|
317
|
+
'extends = "strict"',
|
|
318
|
+
'restrict_network = true',
|
|
319
|
+
// Grok profiles accept directories only. runPinnedGrokCommand replaces
|
|
320
|
+
// this directory with a one-file read-only mount before Grok starts.
|
|
321
|
+
`read_only = [${JSON.stringify(dirname(authorityPublicKey))}]`,
|
|
322
|
+
'',
|
|
323
|
+
].join('\n'), { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
324
|
+
return Object.freeze({ profile, sandboxConfig, authorityPublicKey });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function visibleSentence(language, harness) {
|
|
328
|
+
const names = { claude: 'Claude', codex: 'Codex', grok: 'Grok' };
|
|
329
|
+
return language === 'fr'
|
|
330
|
+
? 'Session active dans ' + names[harness] + '.'
|
|
331
|
+
: 'Sessão ativa no ' + names[harness] + '.';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function canonicalClaudeModel(model) {
|
|
335
|
+
const entry = Object.entries(CLAUDE_NATIVE_MODELS).find(([canonical, native]) => model === canonical || model === native);
|
|
336
|
+
if (!entry) fail('Claude verification requires a supported explicit --claude-model');
|
|
337
|
+
return entry[0];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function nativeModelFor(harness, modelId) {
|
|
341
|
+
if (harness === 'claude') {
|
|
342
|
+
const native = CLAUDE_NATIVE_MODELS[modelId];
|
|
343
|
+
if (!native) fail(`Claude model ${modelId} has no native E2E mapping`);
|
|
344
|
+
return native;
|
|
345
|
+
}
|
|
346
|
+
return modelId;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function permissionPolicyFor(harness) {
|
|
350
|
+
return harness === 'claude'
|
|
351
|
+
? 'router-command-only'
|
|
352
|
+
: harness === 'codex'
|
|
353
|
+
? 'outer-bubblewrap-controlled-project-workspace-write'
|
|
354
|
+
: 'controlled-project-auto-no-subagents-no-web';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function descriptorBoundExecutablePath() {
|
|
358
|
+
if (process.platform !== 'linux') fail('descriptor-bound real harness execution is supported only on Linux');
|
|
359
|
+
// `stdio[3]` below duplicates the parent descriptor into child fd 3 before
|
|
360
|
+
// exec. Resolve the path in that child, never through the mutable parent path.
|
|
361
|
+
return '/proc/self/fd/3';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function writeAll(descriptor, buffer) {
|
|
365
|
+
let offset = 0;
|
|
366
|
+
while (offset < buffer.length) {
|
|
367
|
+
const written = writeSync(descriptor, buffer, offset, buffer.length - offset);
|
|
368
|
+
if (written < 1) fail('could not materialize sealed executable');
|
|
369
|
+
offset += written;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function sealedExecutablePath(directory, executablePath) {
|
|
374
|
+
if (typeof executablePath !== 'string') return join(directory, 'provider');
|
|
375
|
+
const marker = `${process.platform === 'win32' ? '\\' : '/'}node_modules${process.platform === 'win32' ? '\\' : '/'}`;
|
|
376
|
+
const markerIndex = executablePath.indexOf(marker);
|
|
377
|
+
if (markerIndex < 0) return join(directory, 'provider');
|
|
378
|
+
const suffix = executablePath.slice(markerIndex + 1);
|
|
379
|
+
if (!suffix || suffix.split(/[\\/]/).some((part) => !part || part === '.' || part === '..')) {
|
|
380
|
+
fail('verified executable path cannot be safely materialized');
|
|
381
|
+
}
|
|
382
|
+
return join(directory, suffix);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* The source descriptor proves the installed executable binding. The temporary
|
|
387
|
+
* copy preserves Codex's sibling layout, then becomes the input to a private
|
|
388
|
+
* immutable SquashFS mount and is rehashed there before native execution.
|
|
389
|
+
*/
|
|
390
|
+
function copySealedExecutable(sourceRecord, path) {
|
|
391
|
+
const source = sourceRecord?.descriptor;
|
|
392
|
+
if (!Number.isInteger(source) || !sourceRecord?.binding?.executable_sha256 || !sourceRecord?.executable?.snapshot) {
|
|
393
|
+
fail('sealed executable materialization requires a verified source descriptor');
|
|
394
|
+
}
|
|
395
|
+
let target = null;
|
|
396
|
+
let descriptor = null;
|
|
397
|
+
let result;
|
|
398
|
+
let primaryError = null;
|
|
399
|
+
const cleanupFailures = [];
|
|
400
|
+
try {
|
|
401
|
+
const before = fstatSync(source);
|
|
402
|
+
if (!sameSnapshot(before, sourceRecord.executable.snapshot)) fail('provider executable drifted before sealed materialization');
|
|
403
|
+
target = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0), 0o500);
|
|
404
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
405
|
+
let position = 0;
|
|
406
|
+
while (position < before.size) {
|
|
407
|
+
const bytes = readSync(source, buffer, 0, Math.min(buffer.length, before.size - position), position);
|
|
408
|
+
if (bytes < 1) fail('provider executable changed during sealed materialization');
|
|
409
|
+
writeAll(target, buffer.subarray(0, bytes));
|
|
410
|
+
position += bytes;
|
|
411
|
+
}
|
|
412
|
+
fchmodSync(target, 0o500);
|
|
413
|
+
const targetSnapshot = fstatSync(target);
|
|
414
|
+
if (!targetSnapshot.isFile() || targetSnapshot.nlink !== 1 || (targetSnapshot.mode & 0o777) !== 0o500) {
|
|
415
|
+
fail('sealed executable copy is not a private single-link executable');
|
|
416
|
+
}
|
|
417
|
+
if (!sameSnapshot(before, fstatSync(source))) fail('provider executable changed during sealed materialization');
|
|
418
|
+
closeSync(target);
|
|
419
|
+
target = null;
|
|
420
|
+
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
|
|
421
|
+
const executable = digestProviderExecutable(descriptor);
|
|
422
|
+
if (executable.sha256 !== sourceRecord.binding.executable_sha256) {
|
|
423
|
+
fail('sealed executable copy differs from the signed binding');
|
|
424
|
+
}
|
|
425
|
+
result = Object.freeze({ descriptor, path, binding: sourceRecord.binding, executable });
|
|
426
|
+
} catch (error) {
|
|
427
|
+
primaryError = error;
|
|
428
|
+
} finally {
|
|
429
|
+
if (primaryError && descriptor !== null) {
|
|
430
|
+
collectCleanupFailure(cleanupFailures, 'close-failed-sealed-descriptor', () => closeSync(descriptor));
|
|
431
|
+
}
|
|
432
|
+
if (target !== null) collectCleanupFailure(cleanupFailures, 'close-sealed-copy-target', () => closeSync(target));
|
|
433
|
+
if (primaryError) recordCleanupFailure(primaryError, cleanupFailures);
|
|
434
|
+
}
|
|
435
|
+
if (primaryError) throw primaryError;
|
|
436
|
+
throwCleanupFailures(cleanupFailures);
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function materializeSealedExecutable(pinnedExecutable, supportingExecutables = []) {
|
|
441
|
+
if (!Array.isArray(supportingExecutables)) fail('sealed supporting executables must be an array');
|
|
442
|
+
const directory = mkdtempSync(join(tmpdir(), 'chati-real-harness-exec-'));
|
|
443
|
+
let primary = null;
|
|
444
|
+
const supporting = [];
|
|
445
|
+
try {
|
|
446
|
+
chmodSync(directory, 0o700);
|
|
447
|
+
const path = sealedExecutablePath(directory, pinnedExecutable?.binding?.executable_path);
|
|
448
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
449
|
+
primary = copySealedExecutable(pinnedExecutable, path);
|
|
450
|
+
for (const source of supportingExecutables) {
|
|
451
|
+
const supportPath = join(dirname(path), basename(source?.binding?.executable_path || ''));
|
|
452
|
+
if (supportPath === dirname(path) || supportPath === path) fail('sealed supporting executable path is invalid');
|
|
453
|
+
supporting.push(copySealedExecutable(source, supportPath));
|
|
454
|
+
}
|
|
455
|
+
return Object.freeze({
|
|
456
|
+
...primary,
|
|
457
|
+
directory,
|
|
458
|
+
supporting_executables: Object.freeze(supporting),
|
|
459
|
+
});
|
|
460
|
+
} catch (error) {
|
|
461
|
+
const cleanupFailures = [];
|
|
462
|
+
if (primary) collectCleanupFailure(cleanupFailures, 'close-sealed-primary-descriptor', () => closeSync(primary.descriptor));
|
|
463
|
+
for (const support of supporting) {
|
|
464
|
+
collectCleanupFailure(cleanupFailures, 'close-sealed-supporting-descriptor', () => closeSync(support.descriptor));
|
|
465
|
+
}
|
|
466
|
+
collectCleanupFailure(cleanupFailures, 'remove-sealed-executable-directory', () => rmSync(directory, { recursive: true, force: true }));
|
|
467
|
+
recordCleanupFailure(error, cleanupFailures);
|
|
468
|
+
throw error;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function cleanupDetails(failures) {
|
|
473
|
+
return failures.map(({ label, error }) => ({ label, message: error?.message || String(error) }));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function collectCleanupFailure(failures, label, cleanup) {
|
|
477
|
+
try {
|
|
478
|
+
runSecurityTestHook('beforeCleanup', { label });
|
|
479
|
+
cleanup();
|
|
480
|
+
} catch (error) {
|
|
481
|
+
failures.push({ label, error });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function recordCleanupFailure(primaryError, failures) {
|
|
486
|
+
if (!primaryError || failures.length === 0) return;
|
|
487
|
+
primaryError.details = {
|
|
488
|
+
...(primaryError.details || {}),
|
|
489
|
+
cleanup_errors: cleanupDetails(failures),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function throwCleanupFailures(failures) {
|
|
494
|
+
if (failures.length === 0) return;
|
|
495
|
+
const error = new Error('one or more real harness cleanup operations failed');
|
|
496
|
+
error.details = { cleanup_errors: cleanupDetails(failures) };
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function openSandboxDirectory(path, label) {
|
|
501
|
+
if (typeof path !== 'string' || resolve(path) !== path || path === '/') {
|
|
502
|
+
fail(label + ' must be a non-root absolute directory');
|
|
503
|
+
}
|
|
504
|
+
let descriptor;
|
|
505
|
+
try {
|
|
506
|
+
descriptor = openSync(path, constants.O_RDONLY | (constants.O_DIRECTORY || 0) | (constants.O_NOFOLLOW || 0));
|
|
507
|
+
} catch {
|
|
508
|
+
return fail(label + ' must be a directory without a final symlink');
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
if (!fstatSync(descriptor).isDirectory()) fail(label + ' must be a directory');
|
|
512
|
+
return descriptor;
|
|
513
|
+
} catch (error) {
|
|
514
|
+
closeSync(descriptor);
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function assertSandboxDirectoryDescriptor(descriptor, label) {
|
|
520
|
+
if (!Number.isInteger(descriptor)) fail(label + ' requires an open directory descriptor');
|
|
521
|
+
try {
|
|
522
|
+
if (!fstatSync(descriptor).isDirectory()) fail(label + ' requires an open directory descriptor');
|
|
523
|
+
} catch (error) {
|
|
524
|
+
if (error?.message === label + ' requires an open directory descriptor') throw error;
|
|
525
|
+
fail(label + ' requires an open directory descriptor');
|
|
526
|
+
}
|
|
527
|
+
return descriptor;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function projectPathSegments(relativePath, label) {
|
|
531
|
+
if (typeof relativePath !== 'string' || !relativePath || relativePath.startsWith('/')) {
|
|
532
|
+
fail(label + ' must be a non-empty project-relative path');
|
|
533
|
+
}
|
|
534
|
+
const segments = relativePath.split('/');
|
|
535
|
+
if (segments.some((segment) => !segment || segment === '.' || segment === '..')) {
|
|
536
|
+
fail(label + ' must be a normalized project-relative path');
|
|
537
|
+
}
|
|
538
|
+
return segments;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function openPinnedProjectDirectory(projectDescriptor, relativePath = '', label = 'pinned project directory') {
|
|
542
|
+
const pinned = assertSandboxDirectoryDescriptor(projectDescriptor, 'real harness project directory');
|
|
543
|
+
if (process.platform !== 'linux') fail(label + ' requires Linux descriptor traversal');
|
|
544
|
+
const segments = relativePath ? projectPathSegments(relativePath, label) : [];
|
|
545
|
+
let current;
|
|
546
|
+
try {
|
|
547
|
+
current = openSync(
|
|
548
|
+
`/proc/self/fd/${pinned}`,
|
|
549
|
+
constants.O_RDONLY | (constants.O_DIRECTORY || 0),
|
|
550
|
+
);
|
|
551
|
+
for (const segment of segments) {
|
|
552
|
+
const next = openSync(
|
|
553
|
+
join(`/proc/self/fd/${current}`, segment),
|
|
554
|
+
constants.O_RDONLY | (constants.O_DIRECTORY || 0) | (constants.O_NOFOLLOW || 0),
|
|
555
|
+
);
|
|
556
|
+
if (!fstatSync(next).isDirectory()) {
|
|
557
|
+
closeSync(next);
|
|
558
|
+
fail(label + ' must contain only real directories');
|
|
559
|
+
}
|
|
560
|
+
closeSync(current);
|
|
561
|
+
current = next;
|
|
562
|
+
}
|
|
563
|
+
return current;
|
|
564
|
+
} catch (error) {
|
|
565
|
+
if (current !== undefined) closeSync(current);
|
|
566
|
+
if (error?.message?.startsWith(label)) throw error;
|
|
567
|
+
return fail(label + ' must contain only real directories');
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function openPinnedProjectFile(projectDescriptor, relativePath, label) {
|
|
572
|
+
const segments = projectPathSegments(relativePath, label);
|
|
573
|
+
const filename = segments.pop();
|
|
574
|
+
const parent = openPinnedProjectDirectory(projectDescriptor, segments.join('/'), label);
|
|
575
|
+
try {
|
|
576
|
+
return openSync(
|
|
577
|
+
join(`/proc/self/fd/${parent}`, filename),
|
|
578
|
+
constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0),
|
|
579
|
+
);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
if (error?.message?.startsWith(label)) throw error;
|
|
582
|
+
return fail(label + ' must be a bounded regular file');
|
|
583
|
+
} finally {
|
|
584
|
+
closeSync(parent);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export function readPinnedProjectFile(projectDescriptor, relativePath, maxBytes, label) {
|
|
589
|
+
let descriptor;
|
|
590
|
+
try {
|
|
591
|
+
descriptor = openPinnedProjectFile(projectDescriptor, relativePath, label);
|
|
592
|
+
return readBoundedRegularDescriptor(descriptor, maxBytes, label, { path: relativePath });
|
|
593
|
+
} finally {
|
|
594
|
+
if (descriptor !== undefined) closeSync(descriptor);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function releaseProtectedRuntimeEntries(entries) {
|
|
599
|
+
const cleanupFailures = [];
|
|
600
|
+
for (const entry of [...entries].reverse()) {
|
|
601
|
+
if (!entry.descriptorClosed) {
|
|
602
|
+
try {
|
|
603
|
+
closeSync(entry.descriptor);
|
|
604
|
+
entry.descriptorClosed = true;
|
|
605
|
+
} catch (error) {
|
|
606
|
+
cleanupFailures.push({ label: 'close-protected-runtime-descriptor', error });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
throwCleanupFailures(cleanupFailures);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function digestPinnedRuntimeFile(descriptor, snapshot) {
|
|
614
|
+
const digest = createHash('sha256');
|
|
615
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
616
|
+
let position = 0;
|
|
617
|
+
while (position < snapshot.size) {
|
|
618
|
+
const count = readSync(descriptor, buffer, 0, Math.min(buffer.length, snapshot.size - position), position);
|
|
619
|
+
if (count < 1) fail('installed runtime file changed during integrity verification');
|
|
620
|
+
digest.update(buffer.subarray(0, count));
|
|
621
|
+
position += count;
|
|
622
|
+
}
|
|
623
|
+
if (!sameSnapshot(snapshot, fstatSync(descriptor))) fail('installed runtime file changed during integrity verification');
|
|
624
|
+
return digest.digest('hex');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function assertProtectedRuntimeEntries(projectDescriptor, entries) {
|
|
628
|
+
for (const entry of entries) {
|
|
629
|
+
const path = entry.relativePath ? `.chati.dev/${entry.relativePath}` : '.chati.dev';
|
|
630
|
+
let descriptor;
|
|
631
|
+
try {
|
|
632
|
+
descriptor = entry.directory
|
|
633
|
+
? openPinnedProjectDirectory(projectDescriptor, path, 'installed runtime')
|
|
634
|
+
: openPinnedProjectFile(projectDescriptor, path, 'installed runtime');
|
|
635
|
+
if (!sameSnapshot(entry.snapshot, fstatSync(descriptor))) {
|
|
636
|
+
fail('installed runtime changed during provider execution');
|
|
637
|
+
}
|
|
638
|
+
if (!entry.directory && digestPinnedRuntimeFile(descriptor, entry.snapshot) !== entry.sha256) {
|
|
639
|
+
fail('installed runtime file content changed during provider execution');
|
|
640
|
+
}
|
|
641
|
+
} finally {
|
|
642
|
+
if (descriptor !== undefined) closeSync(descriptor);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* The provider CLIs can retain a descriptor-bound CWD but cannot retain
|
|
649
|
+
* arbitrary file descriptors in the shell command they execute. Freeze the
|
|
650
|
+
* installed runtime tree by descriptor before a provider starts so its
|
|
651
|
+
* relative router command and every static or dynamic import observe the
|
|
652
|
+
* same verified non-symlink tree. The verifier never changes the project's
|
|
653
|
+
* inode modes: provider mounts enforce read-only access and post-run snapshots
|
|
654
|
+
* detect any in-place modification, even if the verifier is killed abruptly.
|
|
655
|
+
*/
|
|
656
|
+
export function protectPinnedProjectRuntime(projectDescriptor) {
|
|
657
|
+
const entries = [];
|
|
658
|
+
let released = false;
|
|
659
|
+
const lockEntry = (descriptor, relativePath = '') => {
|
|
660
|
+
const stat = fstatSync(descriptor);
|
|
661
|
+
if (stat.isDirectory()) {
|
|
662
|
+
const snapshot = fstatSync(descriptor);
|
|
663
|
+
entries.push({ descriptor, directory: true, relativePath, snapshot });
|
|
664
|
+
for (const entry of readdirSync(`/proc/self/fd/${descriptor}`)) {
|
|
665
|
+
const child = openSync(
|
|
666
|
+
join(`/proc/self/fd/${descriptor}`, entry),
|
|
667
|
+
constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0),
|
|
668
|
+
);
|
|
669
|
+
lockEntry(child, relativePath ? `${relativePath}/${entry}` : entry);
|
|
670
|
+
}
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (!stat.isFile() || stat.nlink !== 1) {
|
|
674
|
+
closeSync(descriptor);
|
|
675
|
+
fail('installed runtime must contain only regular single-link files and directories');
|
|
676
|
+
}
|
|
677
|
+
const snapshot = fstatSync(descriptor);
|
|
678
|
+
entries.push({
|
|
679
|
+
descriptor, directory: false, relativePath, snapshot,
|
|
680
|
+
sha256: digestPinnedRuntimeFile(descriptor, snapshot),
|
|
681
|
+
});
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
lockEntry(openPinnedProjectDirectory(projectDescriptor, '.chati.dev', 'installed runtime'));
|
|
685
|
+
} catch (error) {
|
|
686
|
+
try { releaseProtectedRuntimeEntries(entries); }
|
|
687
|
+
catch (cleanupError) { recordCleanupFailure(error, [{ label: 'restore-protected-runtime', error: cleanupError }]); }
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
return Object.freeze({
|
|
691
|
+
runtimeDescriptor: entries[0].descriptor,
|
|
692
|
+
assertIntact() { assertProtectedRuntimeEntries(projectDescriptor, entries); },
|
|
693
|
+
release() {
|
|
694
|
+
if (released) return;
|
|
695
|
+
releaseProtectedRuntimeEntries(entries);
|
|
696
|
+
released = true;
|
|
697
|
+
},
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function assertPinnedProjectDirectory(path, descriptor) {
|
|
702
|
+
const pinned = assertSandboxDirectoryDescriptor(descriptor, 'real harness project directory');
|
|
703
|
+
if (path === `/proc/self/fd/${pinned}`) return;
|
|
704
|
+
let currentDescriptor;
|
|
705
|
+
try {
|
|
706
|
+
currentDescriptor = openSync(path, constants.O_RDONLY | (constants.O_DIRECTORY || 0) | (constants.O_NOFOLLOW || 0));
|
|
707
|
+
const current = fstatSync(currentDescriptor);
|
|
708
|
+
const expected = fstatSync(pinned);
|
|
709
|
+
if (!current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) {
|
|
710
|
+
fail('real harness project directory changed after it was pinned');
|
|
711
|
+
}
|
|
712
|
+
} catch {
|
|
713
|
+
fail('real harness project directory changed after it was pinned');
|
|
714
|
+
} finally {
|
|
715
|
+
if (currentDescriptor !== undefined) closeSync(currentDescriptor);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function sameDirectoryIdentity(descriptor, path) {
|
|
720
|
+
let candidate;
|
|
721
|
+
try {
|
|
722
|
+
candidate = openSandboxDirectory(path, 'real Codex protected directory');
|
|
723
|
+
const current = fstatSync(descriptor);
|
|
724
|
+
const protectedDirectory = fstatSync(candidate);
|
|
725
|
+
return current.dev === protectedDirectory.dev && current.ino === protectedDirectory.ino;
|
|
726
|
+
} finally {
|
|
727
|
+
if (candidate !== undefined) closeSync(candidate);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function pathIsInsidePinnedProject(projectDescriptor, path) {
|
|
732
|
+
const projectPath = realpathSync(`/proc/self/fd/${projectDescriptor}`);
|
|
733
|
+
const candidatePath = realpathSync(path);
|
|
734
|
+
const remainder = relative(projectPath, candidatePath);
|
|
735
|
+
return remainder === '' || (remainder !== '..' && !remainder.startsWith('../') && !isAbsolute(remainder));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function assertCodexProjectScope(projectDescriptor, protectedPaths) {
|
|
739
|
+
for (const path of protectedPaths.filter(Boolean)) {
|
|
740
|
+
if (sameDirectoryIdentity(projectDescriptor, path) || pathIsInsidePinnedProject(projectDescriptor, path)) {
|
|
741
|
+
fail('real Codex project directory overlaps a protected host directory');
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function selectCodexEnvironmentCredential() {
|
|
747
|
+
if (typeof process.env.CODEX_ACCESS_TOKEN === 'string' && process.env.CODEX_ACCESS_TOKEN) {
|
|
748
|
+
return Object.freeze({ name: 'CODEX_ACCESS_TOKEN', bytes: Buffer.from(process.env.CODEX_ACCESS_TOKEN, 'utf8') });
|
|
749
|
+
}
|
|
750
|
+
if (typeof process.env.OPENAI_API_KEY === 'string' && process.env.OPENAI_API_KEY) {
|
|
751
|
+
return Object.freeze({ name: 'OPENAI_API_KEY', bytes: Buffer.from(process.env.OPENAI_API_KEY, 'utf8') });
|
|
752
|
+
}
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function sandboxCodexArgs(processCommand, args) {
|
|
757
|
+
const outputPath = processCommand.finalMessagePath || null;
|
|
758
|
+
return args.map((argument) => {
|
|
759
|
+
if (outputPath && argument === outputPath) return join(SANDBOX_OUTPUT_PATH, basename(outputPath));
|
|
760
|
+
return argument;
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function sandboxProviderArgs(args, projectPath) {
|
|
765
|
+
return args.map((argument, index) => args[index - 1] === '--cwd' ? projectPath : argument);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export function runProtectedProviderCommand(processCommand, sealedExecutable, runtimeDescriptor, {
|
|
769
|
+
args = processCommand.args,
|
|
770
|
+
} = {}) {
|
|
771
|
+
if (process.platform !== 'linux' || !existsSync(PYTHON_PATH) || !existsSync(BUBBLEWRAP_PATH)
|
|
772
|
+
|| !existsSync(CODEX_DNS_RUNTIME_PATH) || CODEX_RUNTIME_PATHS.some((path) => !existsSync(path))) {
|
|
773
|
+
fail('real provider E2E requires Linux Python 3 and Bubblewrap runtime protection');
|
|
774
|
+
}
|
|
775
|
+
const executionPath = sealedExecutable?.binding?.executable_path;
|
|
776
|
+
if (!Number.isInteger(sealedExecutable?.descriptor)
|
|
777
|
+
|| !/^[a-f0-9]{64}$/.test(sealedExecutable?.binding?.executable_sha256 || '')
|
|
778
|
+
|| !Number.isInteger(runtimeDescriptor)
|
|
779
|
+
|| typeof executionPath !== 'string' || resolve(executionPath) !== executionPath) {
|
|
780
|
+
fail('real provider E2E requires sealed executable and installed runtime descriptors');
|
|
781
|
+
}
|
|
782
|
+
const projectDescriptor = assertSandboxDirectoryDescriptor(
|
|
783
|
+
processCommand.projectDescriptor,
|
|
784
|
+
'real provider project directory',
|
|
785
|
+
);
|
|
786
|
+
const projectPath = processCommand.projectPath;
|
|
787
|
+
if (typeof projectPath !== 'string' || resolve(projectPath) !== projectPath || projectPath === '/') {
|
|
788
|
+
fail('real provider E2E requires a non-root absolute project path');
|
|
789
|
+
}
|
|
790
|
+
assertSandboxDirectoryDescriptor(runtimeDescriptor, 'installed runtime directory');
|
|
791
|
+
const authorityKeySource = processCommand.authorityPublicKeyPath || resolveE2EAuthorityPublicKeyPath();
|
|
792
|
+
if (typeof authorityKeySource !== 'string' || resolve(authorityKeySource) !== authorityKeySource) {
|
|
793
|
+
fail('real provider E2E requires the installation authority public key');
|
|
794
|
+
}
|
|
795
|
+
const authorityKey = readBoundedRegularFile(authorityKeySource, 64 * 1024, 'installation authority public key');
|
|
796
|
+
const authorityKeyDestination = installationAuthorityPublicKeyPath();
|
|
797
|
+
const authorityKeyDirectory = dirname(authorityKeyDestination);
|
|
798
|
+
const home = homedir();
|
|
799
|
+
const command = [
|
|
800
|
+
'--die-with-parent', '--unshare-all', '--unshare-user', '--share-net', '--disable-userns', '--as-pid-1',
|
|
801
|
+
];
|
|
802
|
+
for (const runtimePath of CODEX_RUNTIME_PATHS) command.push('--ro-bind', runtimePath, runtimePath);
|
|
803
|
+
command.push('--dir', '/run', '--dir', '/run/systemd', '--ro-bind', CODEX_DNS_RUNTIME_PATH, CODEX_DNS_RUNTIME_PATH);
|
|
804
|
+
for (const parent of parentDirectories(home)) command.push('--dir', parent);
|
|
805
|
+
command.push('--ro-bind', home, home);
|
|
806
|
+
// Claude needs its own home-backed authentication state, but a read-only
|
|
807
|
+
// bind of HOME must never make the host installation signing key visible.
|
|
808
|
+
const privateAuthorityPath = installationAuthorityStateDirectory();
|
|
809
|
+
if (existsSync(privateAuthorityPath)) command.push('--tmpfs', privateAuthorityPath);
|
|
810
|
+
if (authorityKeyDirectory !== privateAuthorityPath) command.push('--tmpfs', authorityKeyDirectory);
|
|
811
|
+
command.push(
|
|
812
|
+
'--dev', '/dev', '--proc', '/proc', '--tmpfs', '/tmp',
|
|
813
|
+
// Preserve the canonical project path. The signed authority receipt binds
|
|
814
|
+
// its realpath, so substituting an arbitrary sandbox path would correctly
|
|
815
|
+
// be rejected by the router even though the directory descriptor is sound.
|
|
816
|
+
'--bind-fd', '4', projectPath,
|
|
817
|
+
'--ro-bind', '/proc/self/fd/5', join(projectPath, '.chati.dev'),
|
|
818
|
+
// Native CLIs can locate their immutable companion resources through their
|
|
819
|
+
// own executable path. The Python trampoline hashes the staged descriptor
|
|
820
|
+
// into a sealed memfd before Bubblewrap mounts it at this exact path.
|
|
821
|
+
'--perms', '0500', '--ro-bind-data', '3', executionPath,
|
|
822
|
+
// --ro-bind-data exposes an anonymous memfd with nlink=0. The runtime
|
|
823
|
+
// verifier deliberately rejects that shape for trust anchors, so copy the
|
|
824
|
+
// sealed bytes into a fresh single-link file in the sandbox instead.
|
|
825
|
+
'--perms', '0400', '--file', '{authority_fd}', authorityKeyDestination,
|
|
826
|
+
'--chdir', projectPath, executionPath, ...sandboxProviderArgs(args, projectPath),
|
|
827
|
+
);
|
|
828
|
+
return spawnSync(PYTHON_PATH, ['-I', '-c', SEALED_PROVIDER_RUNNER, JSON.stringify({
|
|
829
|
+
command: BUBBLEWRAP_PATH,
|
|
830
|
+
args: command,
|
|
831
|
+
executable_sha256: sealedExecutable.binding.executable_sha256,
|
|
832
|
+
label: 'Claude executable',
|
|
833
|
+
authority_key_bytes_length: authorityKey.length,
|
|
834
|
+
})], {
|
|
835
|
+
encoding: 'utf8', timeout: 10 * 60 * 1000, maxBuffer: 4 * 1024 * 1024,
|
|
836
|
+
env: { ...process.env, NO_COLOR: '1' }, input: authorityKey,
|
|
837
|
+
stdio: ['pipe', 'pipe', 'pipe', sealedExecutable.descriptor, projectDescriptor, runtimeDescriptor],
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const PINNED_RUNTIME_ENTRY_MONITOR = String.raw`import { watch, writeFileSync } from 'node:fs';
|
|
842
|
+
const [readyPath, changedPath, stoppedPath] = process.argv.slice(1);
|
|
843
|
+
let stopped = false;
|
|
844
|
+
const failClosed = () => {
|
|
845
|
+
if (stopped) return;
|
|
846
|
+
stopped = true;
|
|
847
|
+
try { writeFileSync(changedPath, 'watcher-error\n', { flag: 'wx' }); } catch {}
|
|
848
|
+
process.exitCode = 1;
|
|
849
|
+
};
|
|
850
|
+
let watcher;
|
|
851
|
+
try {
|
|
852
|
+
watcher = watch('/proc/self/fd/3', (eventType, filename) => {
|
|
853
|
+
if (filename === null || filename === '.chati.dev') {
|
|
854
|
+
try { writeFileSync(changedPath, eventType + '\n', { flag: 'wx' }); }
|
|
855
|
+
catch { failClosed(); }
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
watcher.on('error', failClosed);
|
|
859
|
+
writeFileSync(readyPath, 'ready\n', { flag: 'wx' });
|
|
860
|
+
} catch { failClosed(); }
|
|
861
|
+
process.on('SIGUSR1', () => {
|
|
862
|
+
try {
|
|
863
|
+
watcher.close();
|
|
864
|
+
setTimeout(() => {
|
|
865
|
+
try { writeFileSync(stoppedPath, 'stopped\n', { flag: 'wx' }); }
|
|
866
|
+
catch { failClosed(); return; }
|
|
867
|
+
process.exit(0);
|
|
868
|
+
}, 25);
|
|
869
|
+
} catch { failClosed(); }
|
|
870
|
+
});
|
|
871
|
+
setInterval(() => {}, 2 ** 30);`;
|
|
872
|
+
|
|
873
|
+
function pause(milliseconds) {
|
|
874
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function startPinnedRuntimeEntryMonitor(projectDescriptor) {
|
|
878
|
+
const directory = mkdtempSync(join(tmpdir(), 'chati-pinned-runtime-monitor-'));
|
|
879
|
+
const readyPath = join(directory, 'ready');
|
|
880
|
+
const changedPath = join(directory, 'changed');
|
|
881
|
+
const stoppedPath = join(directory, 'stopped');
|
|
882
|
+
let monitor;
|
|
883
|
+
try {
|
|
884
|
+
monitor = spawn(process.execPath, ['--input-type=module', '-e', PINNED_RUNTIME_ENTRY_MONITOR, readyPath, changedPath, stoppedPath], {
|
|
885
|
+
cwd: '/', env: { PATH: process.env.PATH || '/usr/bin:/bin', LANG: 'C.UTF-8' },
|
|
886
|
+
stdio: ['ignore', 'ignore', 'ignore', projectDescriptor],
|
|
887
|
+
});
|
|
888
|
+
const deadline = Date.now() + 1000;
|
|
889
|
+
while (!existsSync(readyPath) && !existsSync(changedPath) && monitor.exitCode === null && Date.now() < deadline) pause(5);
|
|
890
|
+
if (!existsSync(readyPath) || existsSync(changedPath) || monitor.exitCode !== null) {
|
|
891
|
+
fail('could not start pinned installed runtime monitor');
|
|
892
|
+
}
|
|
893
|
+
} catch (error) {
|
|
894
|
+
if (monitor && monitor.exitCode === null) monitor.kill('SIGTERM');
|
|
895
|
+
rmSync(directory, { recursive: true, force: true });
|
|
896
|
+
throw error;
|
|
897
|
+
}
|
|
898
|
+
return Object.freeze({
|
|
899
|
+
release() {
|
|
900
|
+
if (monitor.exitCode === null && !monitor.kill('SIGUSR1')) {
|
|
901
|
+
fail('pinned installed runtime monitor could not begin drain');
|
|
902
|
+
}
|
|
903
|
+
const deadline = Date.now() + 1000;
|
|
904
|
+
while (!existsSync(stoppedPath) && monitor.exitCode === null && Date.now() < deadline) pause(5);
|
|
905
|
+
const observation = Object.freeze({ changed: existsSync(changedPath), stopped: existsSync(stoppedPath) });
|
|
906
|
+
if (!observation.stopped) fail('pinned installed runtime monitor did not drain cleanly');
|
|
907
|
+
rmSync(directory, { recursive: true, force: true });
|
|
908
|
+
return observation;
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Grok's native workspace sandbox creates its own user namespace, so an outer
|
|
915
|
+
* Bubblewrap namespace would fail before Grok can start. Retain the
|
|
916
|
+
* descriptor-bound CWD and fail closed if inotify observes any rename or
|
|
917
|
+
* replacement of .chati.dev while Grok performs its one router invocation.
|
|
918
|
+
*/
|
|
919
|
+
export function runPinnedGrokCommand(processCommand, {
|
|
920
|
+
args = processCommand.args, monitorRuntime = true, sealedExecutable = null,
|
|
921
|
+
} = {}) {
|
|
922
|
+
const projectDescriptor = assertSandboxDirectoryDescriptor(
|
|
923
|
+
processCommand.projectDescriptor,
|
|
924
|
+
'real Grok project directory',
|
|
925
|
+
);
|
|
926
|
+
if (sealedExecutable !== null && (!existsSync(BUBBLEWRAP_PATH)
|
|
927
|
+
|| !Number.isInteger(sealedExecutable?.descriptor)
|
|
928
|
+
|| !/^[a-f0-9]{64}$/.test(sealedExecutable?.binding?.executable_sha256 || '')
|
|
929
|
+
|| typeof sealedExecutable?.binding?.executable_path !== 'string'
|
|
930
|
+
|| resolve(sealedExecutable.binding.executable_path) !== sealedExecutable.binding.executable_path
|
|
931
|
+
|| typeof processCommand?.projectPath !== 'string'
|
|
932
|
+
|| resolve(processCommand.projectPath) !== processCommand.projectPath
|
|
933
|
+
|| !Number.isInteger(processCommand?.runtimeDescriptor)
|
|
934
|
+
|| !existsSync(join(homedir(), '.grok')))) {
|
|
935
|
+
fail('real Grok E2E requires a pinned executable, runtime and Bubblewrap');
|
|
936
|
+
}
|
|
937
|
+
let monitor = null;
|
|
938
|
+
let result;
|
|
939
|
+
let primaryError = null;
|
|
940
|
+
let authorityDescriptor = null;
|
|
941
|
+
const cleanupFailures = [];
|
|
942
|
+
try {
|
|
943
|
+
if (monitorRuntime) monitor = startPinnedRuntimeEntryMonitor(projectDescriptor);
|
|
944
|
+
let executable = processCommand.file;
|
|
945
|
+
let executableArgs = args;
|
|
946
|
+
if (sealedExecutable !== null) {
|
|
947
|
+
let digest;
|
|
948
|
+
try { digest = digestProviderExecutable(sealedExecutable.descriptor); }
|
|
949
|
+
catch { fail('sealed Grok executable differs from its signed binding'); }
|
|
950
|
+
if (digest.sha256 !== sealedExecutable.binding.executable_sha256) {
|
|
951
|
+
fail('sealed Grok executable differs from its signed binding');
|
|
952
|
+
}
|
|
953
|
+
if (typeof processCommand.authorityPublicKeyPath !== 'string'
|
|
954
|
+
|| resolve(processCommand.authorityPublicKeyPath) !== processCommand.authorityPublicKeyPath) {
|
|
955
|
+
fail('real Grok E2E requires the installation authority public key');
|
|
956
|
+
}
|
|
957
|
+
readBoundedRegularFile(processCommand.authorityPublicKeyPath, 64 * 1024, 'installation authority public key');
|
|
958
|
+
authorityDescriptor = openSync(
|
|
959
|
+
processCommand.authorityPublicKeyPath,
|
|
960
|
+
constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0),
|
|
961
|
+
);
|
|
962
|
+
const authorityStat = fstatSync(authorityDescriptor);
|
|
963
|
+
if (!authorityStat.isFile() || authorityStat.nlink !== 1 || authorityStat.size > 64 * 1024) {
|
|
964
|
+
fail('real Grok E2E requires a bounded installation authority public key');
|
|
965
|
+
}
|
|
966
|
+
// Grok's native strict sandbox reopens /proc/self/exe by a linked path.
|
|
967
|
+
// Bind the verified private inode through its descriptor, then remount
|
|
968
|
+
// it read-only at that canonical path. A memfd is kernel-sealed but has
|
|
969
|
+
// no linked pathname, so Grok's nested Bubblewrap cannot execute it.
|
|
970
|
+
executable = BUBBLEWRAP_PATH;
|
|
971
|
+
const grokStateDirectory = join(homedir(), '.grok');
|
|
972
|
+
const privateAuthorityDirectory = installationAuthorityStateDirectory();
|
|
973
|
+
const publicAuthorityDirectory = dirname(processCommand.authorityPublicKeyPath);
|
|
974
|
+
executableArgs = [
|
|
975
|
+
'--die-with-parent', '--unshare-pid', '--ro-bind', '/', '/', '--dev', '/dev', '--proc', '/proc', '--tmpfs', '/tmp',
|
|
976
|
+
// Grok creates local session state before applying its own strict
|
|
977
|
+
// workspace sandbox. Keep that tool-owned state writable, but no
|
|
978
|
+
// other part of HOME is writable in this outer namespace.
|
|
979
|
+
'--bind', grokStateDirectory, grokStateDirectory,
|
|
980
|
+
'--bind-fd', '4', processCommand.projectPath,
|
|
981
|
+
'--ro-bind', '/proc/self/fd/5', join(processCommand.projectPath, '.chati.dev'),
|
|
982
|
+
'--ro-bind', '/proc/self/fd/3', sealedExecutable.binding.executable_path,
|
|
983
|
+
// The strict profile requires a directory. Replace the host directory
|
|
984
|
+
// with one sandbox-owned regular key file, never its private sibling.
|
|
985
|
+
// Mask the canonical private state separately because legacy and
|
|
986
|
+
// custom public-key paths need not share its parent directory.
|
|
987
|
+
'--tmpfs', privateAuthorityDirectory,
|
|
988
|
+
...(resolve(publicAuthorityDirectory) === resolve(privateAuthorityDirectory)
|
|
989
|
+
? []
|
|
990
|
+
: ['--tmpfs', publicAuthorityDirectory]),
|
|
991
|
+
'--perms', '0400', '--file', '6', processCommand.authorityPublicKeyPath,
|
|
992
|
+
'--remount-ro', publicAuthorityDirectory,
|
|
993
|
+
'--chdir', processCommand.projectPath, sealedExecutable.binding.executable_path,
|
|
994
|
+
...sandboxProviderArgs(args, processCommand.projectPath),
|
|
995
|
+
];
|
|
996
|
+
}
|
|
997
|
+
result = spawnSync(executable, executableArgs, {
|
|
998
|
+
cwd: processCommand.cwd,
|
|
999
|
+
encoding: 'utf8', timeout: 10 * 60 * 1000, maxBuffer: 4 * 1024 * 1024,
|
|
1000
|
+
env: grokSandboxEnvironment(process.env),
|
|
1001
|
+
stdio: authorityDescriptor === null ? processCommand.stdio : [...processCommand.stdio, authorityDescriptor],
|
|
1002
|
+
});
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
primaryError = error;
|
|
1005
|
+
throw error;
|
|
1006
|
+
} finally {
|
|
1007
|
+
if (authorityDescriptor !== null) {
|
|
1008
|
+
collectCleanupFailure(cleanupFailures, 'close-Grok-authority-public-key-descriptor', () => closeSync(authorityDescriptor));
|
|
1009
|
+
}
|
|
1010
|
+
if (monitor) {
|
|
1011
|
+
let observation = null;
|
|
1012
|
+
collectCleanupFailure(cleanupFailures, 'stop-Grok-runtime-entry-monitor', () => { observation = monitor.release(); });
|
|
1013
|
+
if (observation?.changed && !primaryError) primaryError = new Error('installed runtime directory entry changed during Grok execution');
|
|
1014
|
+
}
|
|
1015
|
+
if (primaryError) recordCleanupFailure(primaryError, cleanupFailures);
|
|
1016
|
+
else throwCleanupFailures(cleanupFailures);
|
|
1017
|
+
}
|
|
1018
|
+
if (primaryError) throw primaryError;
|
|
1019
|
+
return result;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export function grokSandboxEnvironment(environment) {
|
|
1023
|
+
const allowed = {};
|
|
1024
|
+
for (const [key, value] of Object.entries(environment || {})) {
|
|
1025
|
+
if (GROK_RUNTIME_ENVIRONMENT_KEYS.has(key)
|
|
1026
|
+
|| GROK_RUNTIME_CREDENTIAL_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
1027
|
+
allowed[key] = value;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return Object.freeze({ ...allowed, PATH: GROK_SYSTEM_PATH, NO_COLOR: '1' });
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function runProtectedRouterExit(projectDescriptor, runtimeDescriptor, projectPath) {
|
|
1034
|
+
assertSandboxDirectoryDescriptor(projectDescriptor, 'real harness project directory');
|
|
1035
|
+
assertSandboxDirectoryDescriptor(runtimeDescriptor, 'installed runtime directory');
|
|
1036
|
+
if (typeof projectPath !== 'string' || resolve(projectPath) !== projectPath || projectPath === '/') {
|
|
1037
|
+
fail('protected router exit requires a non-root absolute project path');
|
|
1038
|
+
}
|
|
1039
|
+
const home = homedir();
|
|
1040
|
+
const command = ['--die-with-parent', '--unshare-all', '--unshare-user', '--share-net', '--disable-userns', '--as-pid-1'];
|
|
1041
|
+
for (const runtimePath of CODEX_RUNTIME_PATHS) command.push('--ro-bind', runtimePath, runtimePath);
|
|
1042
|
+
for (const parent of parentDirectories(home)) command.push('--dir', parent);
|
|
1043
|
+
command.push(
|
|
1044
|
+
'--dir', '/run', '--dir', '/run/systemd', '--ro-bind', CODEX_DNS_RUNTIME_PATH, CODEX_DNS_RUNTIME_PATH,
|
|
1045
|
+
'--ro-bind', home, home,
|
|
1046
|
+
'--dev', '/dev', '--proc', '/proc', '--tmpfs', '/tmp',
|
|
1047
|
+
'--bind-fd', '3', projectPath, '--ro-bind', '/proc/self/fd/4', join(projectPath, '.chati.dev'),
|
|
1048
|
+
'--chdir', projectPath, process.execPath, '.chati.dev/orchestrator/chati-router.js', 'exit',
|
|
1049
|
+
);
|
|
1050
|
+
return spawnSync(BUBBLEWRAP_PATH, command, {
|
|
1051
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024,
|
|
1052
|
+
env: { PATH: process.env.PATH || '/usr/bin:/bin', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' },
|
|
1053
|
+
stdio: ['ignore', 'pipe', 'pipe', projectDescriptor, runtimeDescriptor],
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
export function runSealedCodexCommand(processCommand, sealedExecutable, { args = processCommand.args } = {}) {
|
|
1058
|
+
if (process.platform !== 'linux' || !existsSync(PYTHON_PATH) || !existsSync(BUBBLEWRAP_PATH)
|
|
1059
|
+
|| !existsSync(CODEX_DNS_RUNTIME_PATH) || CODEX_RUNTIME_PATHS.some((path) => !existsSync(path))) {
|
|
1060
|
+
fail('real Codex E2E requires Linux Python 3 and Bubblewrap sealed execution');
|
|
1061
|
+
}
|
|
1062
|
+
const supportingExecutable = sealedExecutable?.supporting_executables?.[0];
|
|
1063
|
+
if (!Number.isInteger(sealedExecutable?.descriptor)
|
|
1064
|
+
|| !/^[a-f0-9]{64}$/.test(sealedExecutable?.binding?.executable_sha256 || '')
|
|
1065
|
+
|| !Number.isInteger(supportingExecutable?.descriptor)
|
|
1066
|
+
|| !/^[a-f0-9]{64}$/.test(supportingExecutable?.binding?.executable_sha256 || '')) {
|
|
1067
|
+
fail('sealed Codex command requires pinned primary and code-mode host descriptors');
|
|
1068
|
+
}
|
|
1069
|
+
const runtimeDescriptor = assertSandboxDirectoryDescriptor(
|
|
1070
|
+
processCommand.runtimeDescriptor,
|
|
1071
|
+
'installed runtime directory',
|
|
1072
|
+
);
|
|
1073
|
+
if (typeof processCommand.cwd !== 'string'
|
|
1074
|
+
|| processCommand.cwd === '/tmp'
|
|
1075
|
+
|| ['/run', '/state', '/output'].some((reserved) => processCommand.cwd === reserved || processCommand.cwd.startsWith(reserved + '/'))) {
|
|
1076
|
+
fail('real Codex project directory collides with a reserved sandbox mount');
|
|
1077
|
+
}
|
|
1078
|
+
const credential = selectCodexEnvironmentCredential();
|
|
1079
|
+
const configuredCodexHome = process.env.CODEX_HOME || join(homedir(), '.codex');
|
|
1080
|
+
const authPath = join(configuredCodexHome, 'auth.json');
|
|
1081
|
+
const auth = credential ? Buffer.alloc(0) : readBoundedRegularFile(authPath, 1024 * 1024, 'Codex auth');
|
|
1082
|
+
if (!credential && auth.length === 0) fail('real Codex E2E requires its existing auth.json or an environment credential');
|
|
1083
|
+
const authorityKeyDestination = installationAuthorityPublicKeyPath();
|
|
1084
|
+
const authorityKeySource = processCommand.authorityPublicKeyPath || resolveE2EAuthorityPublicKeyPath();
|
|
1085
|
+
if (typeof authorityKeySource !== 'string' || resolve(authorityKeySource) !== authorityKeySource) {
|
|
1086
|
+
fail('real Codex E2E requires the installation authority public key');
|
|
1087
|
+
}
|
|
1088
|
+
const authorityKey = readBoundedRegularFile(authorityKeySource, 64 * 1024, 'installation authority public key');
|
|
1089
|
+
const stateDirectory = mkdtempSync(join(tmpdir(), 'chati-real-codex-state-'));
|
|
1090
|
+
let projectDescriptor = null;
|
|
1091
|
+
let ownsProjectDescriptor = false;
|
|
1092
|
+
let outputDescriptor = null;
|
|
1093
|
+
let stateDescriptor = null;
|
|
1094
|
+
let result;
|
|
1095
|
+
let primaryError = null;
|
|
1096
|
+
const cleanupFailures = [];
|
|
1097
|
+
try {
|
|
1098
|
+
chmodSync(stateDirectory, 0o700);
|
|
1099
|
+
projectDescriptor = processCommand.projectDescriptor === undefined
|
|
1100
|
+
? openSandboxDirectory(processCommand.cwd, 'real Codex project directory')
|
|
1101
|
+
: assertSandboxDirectoryDescriptor(processCommand.projectDescriptor, 'real Codex project directory');
|
|
1102
|
+
ownsProjectDescriptor = processCommand.projectDescriptor === undefined;
|
|
1103
|
+
stateDescriptor = openSandboxDirectory(stateDirectory, 'real Codex ephemeral state directory');
|
|
1104
|
+
const outputDirectory = processCommand.finalMessagePath ? dirname(processCommand.finalMessagePath) : null;
|
|
1105
|
+
if (outputDirectory) outputDescriptor = openSandboxDirectory(outputDirectory, 'real Codex final-message directory');
|
|
1106
|
+
assertCodexProjectScope(projectDescriptor, [
|
|
1107
|
+
'/tmp', tmpdir(), existsSync(dirname(authPath)) ? dirname(authPath) : null,
|
|
1108
|
+
dirname(authorityKeySource), outputDirectory,
|
|
1109
|
+
]);
|
|
1110
|
+
const input = Buffer.concat([credential?.bytes || Buffer.alloc(0), auth, authorityKey]);
|
|
1111
|
+
runSecurityTestHook('beforeCodexSandboxLaunch', { authorityKeySource, authPath, stateDirectory });
|
|
1112
|
+
result = spawnSync(PYTHON_PATH, [
|
|
1113
|
+
'-I', '-c', SEALED_CODEX_RUNNER,
|
|
1114
|
+
JSON.stringify({
|
|
1115
|
+
args: sandboxCodexArgs(processCommand, args),
|
|
1116
|
+
executable_sha256: sealedExecutable.binding.executable_sha256,
|
|
1117
|
+
supporting_executable_sha256: supportingExecutable.binding.executable_sha256,
|
|
1118
|
+
bubblewrap_path: BUBBLEWRAP_PATH,
|
|
1119
|
+
runtime_paths: CODEX_RUNTIME_PATHS,
|
|
1120
|
+
dns_runtime_path: CODEX_DNS_RUNTIME_PATH,
|
|
1121
|
+
sealed_executable_path: SEALED_CODEX_EXECUTABLE_PATH,
|
|
1122
|
+
sealed_host_path: SEALED_CODEX_HOST_PATH,
|
|
1123
|
+
project_fd: 5,
|
|
1124
|
+
project_path: processCommand.cwd,
|
|
1125
|
+
output_fd: outputDescriptor === null ? null : 6,
|
|
1126
|
+
output_path: SANDBOX_OUTPUT_PATH,
|
|
1127
|
+
state_fd: 7,
|
|
1128
|
+
state_path: SANDBOX_STATE_PATH,
|
|
1129
|
+
runtime_fd: 8,
|
|
1130
|
+
runtime_path: join(processCommand.cwd, '.chati.dev'),
|
|
1131
|
+
tmp_path: SANDBOX_TMP_PATH,
|
|
1132
|
+
credential_name: credential?.name || null,
|
|
1133
|
+
credential_bytes_length: credential?.bytes.length || 0,
|
|
1134
|
+
auth_bytes_length: auth.length,
|
|
1135
|
+
authority_key_bytes_length: authorityKey.length,
|
|
1136
|
+
authority_key_destination: authorityKeyDestination,
|
|
1137
|
+
authority_key_directory: dirname(authorityKeyDestination),
|
|
1138
|
+
authority_key_parent_directories: parentDirectories(dirname(authorityKeyDestination)),
|
|
1139
|
+
test_host_pid: Number.isInteger(processCommand.sandboxTestHostPid) ? processCommand.sandboxTestHostPid : null,
|
|
1140
|
+
}),
|
|
1141
|
+
], {
|
|
1142
|
+
cwd: '/',
|
|
1143
|
+
encoding: 'utf8',
|
|
1144
|
+
env: { PATH: process.env.PATH || '/usr/bin:/bin', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' },
|
|
1145
|
+
input,
|
|
1146
|
+
timeout: 10 * 60 * 1000,
|
|
1147
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
1148
|
+
stdio: [
|
|
1149
|
+
'pipe', 'pipe', 'pipe', sealedExecutable.descriptor, supportingExecutable.descriptor,
|
|
1150
|
+
projectDescriptor, outputDescriptor === null ? 'ignore' : outputDescriptor, stateDescriptor, runtimeDescriptor,
|
|
1151
|
+
],
|
|
1152
|
+
});
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
primaryError = error;
|
|
1155
|
+
throw error;
|
|
1156
|
+
} finally {
|
|
1157
|
+
if (ownsProjectDescriptor && projectDescriptor !== null) collectCleanupFailure(cleanupFailures, 'close-Codex-project-directory-descriptor', () => closeSync(projectDescriptor));
|
|
1158
|
+
if (outputDescriptor !== null) collectCleanupFailure(cleanupFailures, 'close-Codex-final-message-directory-descriptor', () => closeSync(outputDescriptor));
|
|
1159
|
+
if (stateDescriptor !== null) collectCleanupFailure(cleanupFailures, 'close-Codex-ephemeral-state-directory-descriptor', () => closeSync(stateDescriptor));
|
|
1160
|
+
collectCleanupFailure(cleanupFailures, 'remove-Codex-ephemeral-state-directory', () => rmSync(stateDirectory, { recursive: true, force: true }));
|
|
1161
|
+
if (primaryError) recordCleanupFailure(primaryError, cleanupFailures);
|
|
1162
|
+
}
|
|
1163
|
+
throwCleanupFailures(cleanupFailures);
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export function commandFor(harness, projectDir, prompt, {
|
|
1168
|
+
invocation,
|
|
1169
|
+
codexFinalMessagePath = null,
|
|
1170
|
+
grokSandboxProfile = null,
|
|
1171
|
+
authorityPublicKeyPath = null,
|
|
1172
|
+
pinnedDescriptor = null,
|
|
1173
|
+
projectDescriptor = null,
|
|
1174
|
+
runtimeDescriptor = null,
|
|
1175
|
+
} = {}) {
|
|
1176
|
+
if (!invocation?.model_pin?.model_id || !invocation?.model_pin?.reasoning_configuration || pinnedDescriptor === null) {
|
|
1177
|
+
fail(`real ${harness} requires a sealed invocation and pinned executable descriptor`);
|
|
1178
|
+
}
|
|
1179
|
+
const executablePath = descriptorBoundExecutablePath();
|
|
1180
|
+
const nativeModel = nativeModelFor(harness, invocation.model_pin.model_id);
|
|
1181
|
+
// Provider CLIs inherit the descriptor-bound current working directory, but
|
|
1182
|
+
// their shell tools do not inherit arbitrary extra file descriptors. Keep
|
|
1183
|
+
// the router command relative to that pinned CWD; every verifier read around
|
|
1184
|
+
// the command traverses the project descriptor with O_NOFOLLOW.
|
|
1185
|
+
const pinnedProjectPath = projectDescriptor === null ? projectDir : '/proc/self/fd/4';
|
|
1186
|
+
const common = {
|
|
1187
|
+
file: executablePath,
|
|
1188
|
+
projectPath: projectDir,
|
|
1189
|
+
cwd: harness === 'codex' ? projectDir : pinnedProjectPath,
|
|
1190
|
+
...(authorityPublicKeyPath === null ? {} : { authorityPublicKeyPath }),
|
|
1191
|
+
stdio: projectDescriptor === null
|
|
1192
|
+
? ['ignore', 'pipe', 'pipe', pinnedDescriptor]
|
|
1193
|
+
: [
|
|
1194
|
+
'ignore', 'pipe', 'pipe', pinnedDescriptor, projectDescriptor,
|
|
1195
|
+
...(runtimeDescriptor === null ? [] : [runtimeDescriptor]),
|
|
1196
|
+
],
|
|
1197
|
+
...(projectDescriptor === null ? {} : {
|
|
1198
|
+
projectDescriptor,
|
|
1199
|
+
...(runtimeDescriptor === null ? {} : { runtimeDescriptor }),
|
|
1200
|
+
}),
|
|
1201
|
+
};
|
|
1202
|
+
if (harness === 'claude') {
|
|
1203
|
+
return {
|
|
1204
|
+
...common,
|
|
1205
|
+
args: [
|
|
1206
|
+
'-p', '--safe-mode', '--strict-mcp-config', '--no-session-persistence',
|
|
1207
|
+
'--tools', 'Bash', '--model', nativeModel, '--effort', invocation.model_pin.reasoning_configuration,
|
|
1208
|
+
'--allowedTools', 'Bash(node .chati.dev/orchestrator/chati-router.js *)',
|
|
1209
|
+
'--', prompt,
|
|
1210
|
+
],
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
if (harness === 'codex') {
|
|
1214
|
+
if (!codexFinalMessagePath) fail('Codex verification requires a final-message output path');
|
|
1215
|
+
return {
|
|
1216
|
+
...common,
|
|
1217
|
+
args: [
|
|
1218
|
+
'exec', '-C', projectDir, '--sandbox', 'danger-full-access', '--ephemeral', '--ignore-user-config', '--skip-git-repo-check', '--color', 'never',
|
|
1219
|
+
'-m', nativeModel,
|
|
1220
|
+
'-c', `model_reasoning_effort=${JSON.stringify(invocation.model_pin.reasoning_configuration)}`,
|
|
1221
|
+
'--output-last-message', codexFinalMessagePath, prompt,
|
|
1222
|
+
],
|
|
1223
|
+
finalMessagePath: codexFinalMessagePath,
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
if (typeof grokSandboxProfile !== 'string' || !/^[a-z0-9-]{24,128}$/.test(grokSandboxProfile)) {
|
|
1227
|
+
fail('real Grok E2E requires a unique strict sandbox profile');
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
...common,
|
|
1231
|
+
args: [
|
|
1232
|
+
'--cwd', pinnedProjectPath, '--single', prompt, '--permission-mode', 'auto', '--sandbox', grokSandboxProfile,
|
|
1233
|
+
'--tools', 'Bash', '--no-subagents', '--disable-web-search', '--no-plan', '--max-turns', '3',
|
|
1234
|
+
'--output-format', 'plain', '-m', nativeModel, '--reasoning-effort', invocation.model_pin.reasoning_configuration,
|
|
1235
|
+
],
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function promptFor(language, harness, invocation) {
|
|
1240
|
+
const visible = visibleSentence(language, harness);
|
|
1241
|
+
const languageName = language === 'fr' ? 'French' : 'Brazilian Portuguese';
|
|
1242
|
+
return [
|
|
1243
|
+
'This is a non-interactive deterministic Chati.dev installed-runtime verification.',
|
|
1244
|
+
`route_reason=${E2E_ROUTE_REASON}.`,
|
|
1245
|
+
`provider_id=${invocation.provider_id}; model_id=${invocation.model_pin.model_id}; reasoning_configuration=${invocation.model_pin.reasoning_configuration}.`,
|
|
1246
|
+
'The harness has pre-authorized exactly one local shell command: node .chati.dev/orchestrator/chati-router.js --harness ' + harness,
|
|
1247
|
+
'Run that command once with the shell. Do not edit files and do not run another provider command.',
|
|
1248
|
+
'Inspect its JSON result. If it fails or does not report a successful active session for ' + harness + ', report the observed failure and do not emit a receipt.',
|
|
1249
|
+
'Only if the command succeeds, emit the following two lines and nothing else. The visible sentence must be in ' + languageName + '.',
|
|
1250
|
+
visible,
|
|
1251
|
+
'CHATI_REAL_E2E {"harness":"' + harness + '","language":"' + language + '","router_ok":true,"active_harness":"' + harness + '"}',
|
|
1252
|
+
'That receipt states only the successful result you just observed from the local command.',
|
|
1253
|
+
].join('\n');
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export function parseMarker(output, harness) {
|
|
1257
|
+
const matches = [...output.matchAll(/CHATI_REAL_E2E\s+(\{[^\n]+\})/g)];
|
|
1258
|
+
if (matches.length !== 1) fail('real ' + harness + ' process did not emit exactly one E2E marker', { output: output.slice(-10_000) });
|
|
1259
|
+
try { return JSON.parse(matches[0][1]); } catch {
|
|
1260
|
+
return fail('real ' + harness + ' process emitted malformed E2E JSON');
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export function readCodexFinalMessage(path) {
|
|
1265
|
+
return readBoundedRegularFile(path, 1024 * 1024, 'real codex final message').toString('utf8');
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
export function validateNativeLocks(projectDir, { active = true, projectDescriptor = null } = {}) {
|
|
1269
|
+
for (const path of ['CLAUDE.local.md', 'AGENTS.override.md', '.grok/session-lock.md']) {
|
|
1270
|
+
const content = projectDescriptor === null
|
|
1271
|
+
? readBoundedRegularFile(join(projectDir, path), 1024 * 1024, 'native session projection').toString('utf8')
|
|
1272
|
+
: readPinnedProjectFile(projectDescriptor, path, 1024 * 1024, 'native session projection').toString('utf8');
|
|
1273
|
+
if (active && (!/Session Lock: ACTIVE/.test(content) || /## Session Paused/.test(content))) {
|
|
1274
|
+
fail('native session projection is not active: ' + path);
|
|
1275
|
+
}
|
|
1276
|
+
if (!active && (/Session Lock: ACTIVE/.test(content) || !/Status: INACTIVE/.test(content))) {
|
|
1277
|
+
fail('native session projection is not inactive: ' + path);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function releaseCandidateEvidence(tarballSha256, packageVersion) {
|
|
1283
|
+
if (tarballSha256 === null && packageVersion === null) return null;
|
|
1284
|
+
if (!/^[a-f0-9]{64}$/.test(tarballSha256 || '') || typeof packageVersion !== 'string' || !packageVersion) {
|
|
1285
|
+
fail('release candidate evidence requires a tarball SHA-256 and package version');
|
|
1286
|
+
}
|
|
1287
|
+
const installedPackage = JSON.parse(readBoundedRegularFile(
|
|
1288
|
+
join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'),
|
|
1289
|
+
1024 * 1024,
|
|
1290
|
+
'installed package metadata',
|
|
1291
|
+
).toString('utf8'));
|
|
1292
|
+
if (installedPackage.name !== 'chati-dev' || installedPackage.version !== packageVersion) {
|
|
1293
|
+
fail('installed real harness verifier package does not match release candidate metadata');
|
|
1294
|
+
}
|
|
1295
|
+
return Object.freeze({ tarball_sha256: tarballSha256, package_version: packageVersion });
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function assertPinnedInstallationAuthority(projectRoot, installationBytes, expectedInstallation) {
|
|
1299
|
+
const revalidated = loadRuntimeInstallationV2(projectRoot, {
|
|
1300
|
+
source: { content: installationBytes.toString('utf8') },
|
|
1301
|
+
});
|
|
1302
|
+
if (!revalidated || revalidated.digest !== expectedInstallation.digest
|
|
1303
|
+
|| revalidated.installation_artifact_id !== expectedInstallation.installation_artifact_id) {
|
|
1304
|
+
fail('signed installation authority changed during real harness execution');
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
export function runRealHarnessE2E({
|
|
1309
|
+
projectDir,
|
|
1310
|
+
language,
|
|
1311
|
+
claudeModel = null,
|
|
1312
|
+
harnesses = REAL_HARNESS_SEQUENCE,
|
|
1313
|
+
tarballSha256 = null,
|
|
1314
|
+
packageVersion = null,
|
|
1315
|
+
}) {
|
|
1316
|
+
const projectRoot = resolve(projectDir);
|
|
1317
|
+
if (!['pt', 'fr'].includes(language)) fail('language must be pt or fr');
|
|
1318
|
+
if (!Array.isArray(harnesses) || harnesses.length === 0
|
|
1319
|
+
|| harnesses.length > REAL_HARNESS_SEQUENCE.length
|
|
1320
|
+
|| harnesses.some((harness, index) => harness !== REAL_HARNESS_SEQUENCE[index])) {
|
|
1321
|
+
fail('harnesses must be a non-empty ordered prefix of claude,codex,grok');
|
|
1322
|
+
}
|
|
1323
|
+
const projectDescriptor = openSandboxDirectory(projectRoot, 'real harness project directory');
|
|
1324
|
+
let runtimeProtection = null;
|
|
1325
|
+
let outerError = null;
|
|
1326
|
+
try {
|
|
1327
|
+
const root = `/proc/self/fd/${projectDescriptor}`;
|
|
1328
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1329
|
+
const routerPath = '.chati.dev/orchestrator/chati-router.js';
|
|
1330
|
+
const installationPath = '.chati/v2/installation.json';
|
|
1331
|
+
const installationBefore = readPinnedProjectFile(projectDescriptor, installationPath, 4 * 1024 * 1024, 'signed installation');
|
|
1332
|
+
const installation = loadRuntimeInstallationV2(root, {
|
|
1333
|
+
source: { content: installationBefore.toString('utf8') },
|
|
1334
|
+
});
|
|
1335
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1336
|
+
if (!installation) fail('project must have an authorized v2 installation');
|
|
1337
|
+
runtimeProtection = protectPinnedProjectRuntime(projectDescriptor);
|
|
1338
|
+
const routerBefore = readPinnedProjectFile(projectDescriptor, routerPath, 4 * 1024 * 1024, 'installed runtime router');
|
|
1339
|
+
if (routerBefore.length === 0) fail('project must be a complete installed v2 runtime');
|
|
1340
|
+
if (typeof claudeModel !== 'string' || !claudeModel.trim()) {
|
|
1341
|
+
fail('real harness E2E requires an explicit --claude-model');
|
|
1342
|
+
}
|
|
1343
|
+
const releaseCandidate = releaseCandidateEvidence(tarballSha256, packageVersion);
|
|
1344
|
+
const requestedClaudeModel = canonicalClaudeModel(claudeModel.trim());
|
|
1345
|
+
const authorityPublicKey = resolveE2EAuthorityPublicKeyPath();
|
|
1346
|
+
const grokSandbox = harnesses.includes('grok')
|
|
1347
|
+
? configureGrokStrictSandbox(projectRoot, authorityPublicKey)
|
|
1348
|
+
: null;
|
|
1349
|
+
const results = [];
|
|
1350
|
+
|
|
1351
|
+
for (const harness of harnesses) {
|
|
1352
|
+
const codexOutputDir = harness === 'codex'
|
|
1353
|
+
? mkdtempSync(join(tmpdir(), 'chati-real-codex-output-'))
|
|
1354
|
+
: null;
|
|
1355
|
+
let pinnedExecutable = null;
|
|
1356
|
+
let pinnedSupportingExecutables = [];
|
|
1357
|
+
let sealedExecutable = null;
|
|
1358
|
+
let primaryError = null;
|
|
1359
|
+
const cleanupFailures = [];
|
|
1360
|
+
try {
|
|
1361
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1362
|
+
const selected = installation.installation.enabled_providers.find((binding) => binding.harness_id === harness);
|
|
1363
|
+
if (!selected) fail('real ' + harness + ' is not selected by the signed installation');
|
|
1364
|
+
pinnedExecutable = openPinnedProviderExecutable(selected, { harnessId: harness, projectDir: root });
|
|
1365
|
+
pinnedSupportingExecutables = openPinnedProviderSupportingExecutables(selected, { harnessId: harness, projectDir: root });
|
|
1366
|
+
if (harness === 'codex' && pinnedSupportingExecutables.length !== 1) {
|
|
1367
|
+
fail('real Codex E2E requires one sealed code-mode host supporting executable');
|
|
1368
|
+
}
|
|
1369
|
+
sealedExecutable = materializeSealedExecutable(pinnedExecutable, pinnedSupportingExecutables);
|
|
1370
|
+
const invocation = resolveRuntimeRailInvocationV2({
|
|
1371
|
+
artifact: installation,
|
|
1372
|
+
action: 'review',
|
|
1373
|
+
binding: { provider_id: selected.provider_id, harness_id: harness },
|
|
1374
|
+
...(harness === 'claude' ? { model_id: requestedClaudeModel } : {}),
|
|
1375
|
+
reasoning_configuration: 'high',
|
|
1376
|
+
});
|
|
1377
|
+
const prompt = promptFor(language, harness, invocation);
|
|
1378
|
+
const executionExecutable = sealedExecutable;
|
|
1379
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1380
|
+
readPinnedProjectFile(projectDescriptor, routerPath, 4 * 1024 * 1024, 'installed runtime router');
|
|
1381
|
+
const processCommand = commandFor(harness, projectRoot, prompt, {
|
|
1382
|
+
invocation,
|
|
1383
|
+
pinnedDescriptor: executionExecutable.descriptor,
|
|
1384
|
+
projectDescriptor,
|
|
1385
|
+
runtimeDescriptor: runtimeProtection.runtimeDescriptor,
|
|
1386
|
+
authorityPublicKeyPath: authorityPublicKey,
|
|
1387
|
+
...(harness === 'grok' ? { grokSandboxProfile: grokSandbox.profile } : {}),
|
|
1388
|
+
...(codexOutputDir ? { codexFinalMessagePath: join(codexOutputDir, 'final.txt') } : {}),
|
|
1389
|
+
});
|
|
1390
|
+
const versionResult = harness === 'codex'
|
|
1391
|
+
? runSealedCodexCommand(processCommand, executionExecutable, { args: ['--version'] })
|
|
1392
|
+
: harness === 'grok'
|
|
1393
|
+
? runPinnedGrokCommand(processCommand, {
|
|
1394
|
+
args: ['--version'], monitorRuntime: false, sealedExecutable: executionExecutable,
|
|
1395
|
+
})
|
|
1396
|
+
: runProtectedProviderCommand(processCommand, executionExecutable, runtimeProtection.runtimeDescriptor, { args: ['--version'] });
|
|
1397
|
+
if (versionResult.error || versionResult.status !== 0) {
|
|
1398
|
+
fail('real ' + harness + ' version probe failed', {
|
|
1399
|
+
status: versionResult.status,
|
|
1400
|
+
signal: versionResult.signal,
|
|
1401
|
+
error: versionResult.error?.message,
|
|
1402
|
+
output: ((versionResult.stdout || '') + '\n' + (versionResult.stderr || '')).slice(-20_000),
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
runtimeProtection.assertIntact();
|
|
1406
|
+
const processResult = harness === 'codex'
|
|
1407
|
+
? runSealedCodexCommand(processCommand, executionExecutable)
|
|
1408
|
+
: harness === 'grok'
|
|
1409
|
+
? runPinnedGrokCommand(processCommand, { sealedExecutable: executionExecutable })
|
|
1410
|
+
: runProtectedProviderCommand(processCommand, executionExecutable, runtimeProtection.runtimeDescriptor);
|
|
1411
|
+
const output = (processResult.stdout || '') + '\n' + (processResult.stderr || '');
|
|
1412
|
+
if (processResult.error || processResult.status !== 0) {
|
|
1413
|
+
fail('real ' + harness + ' process failed', {
|
|
1414
|
+
status: processResult.status,
|
|
1415
|
+
signal: processResult.signal,
|
|
1416
|
+
error: processResult.error?.message,
|
|
1417
|
+
output: output.slice(-20_000),
|
|
1418
|
+
completed: results,
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
if (harness === 'grok' && /sandbox could not be applied/i.test(output)) {
|
|
1422
|
+
fail('real Grok E2E custom strict sandbox was not applied', { output: output.slice(-20_000) });
|
|
1423
|
+
}
|
|
1424
|
+
runtimeProtection.assertIntact();
|
|
1425
|
+
const visibleOutput = processCommand.finalMessagePath
|
|
1426
|
+
? readCodexFinalMessage(processCommand.finalMessagePath)
|
|
1427
|
+
: (processResult.stdout || '');
|
|
1428
|
+
if (/failed to parse hooks config|unknown field [`'"]?\$comment/i.test(processResult.stderr || '')) {
|
|
1429
|
+
fail('real ' + harness + ' process rejected its installed native configuration', { output: (processResult.stderr || '').slice(-10_000) });
|
|
1430
|
+
}
|
|
1431
|
+
const marker = parseMarker(visibleOutput, harness);
|
|
1432
|
+
const expected = { harness, language, router_ok: true, active_harness: harness };
|
|
1433
|
+
if (JSON.stringify(marker) !== JSON.stringify(expected)) {
|
|
1434
|
+
fail('real ' + harness + ' marker does not match the router contract', {
|
|
1435
|
+
marker, expected, visible_output: visibleOutput.slice(-10_000), process_output: output.slice(-10_000),
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
if (!visibleOutput.includes(visibleSentence(language, harness))) fail('real ' + harness + ' output did not preserve visible language');
|
|
1439
|
+
const session = yaml.load(readPinnedProjectFile(
|
|
1440
|
+
projectDescriptor, '.chati/session.yaml', 4 * 1024 * 1024, 'canonical session',
|
|
1441
|
+
).toString('utf8'));
|
|
1442
|
+
if (session.language !== language || session.active_harness !== harness || session.session_status !== 'active') {
|
|
1443
|
+
fail('real ' + harness + ' process did not activate the expected canonical session', {
|
|
1444
|
+
language: session.language,
|
|
1445
|
+
active_harness: session.active_harness,
|
|
1446
|
+
session_status: session.session_status,
|
|
1447
|
+
visible_output: visibleOutput.slice(-10_000),
|
|
1448
|
+
process_output: output.slice(-10_000),
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
validateNativeLocks(root, { projectDescriptor });
|
|
1452
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1453
|
+
if (!readPinnedProjectFile(projectDescriptor, installationPath, 4 * 1024 * 1024, 'signed installation').equals(installationBefore)) {
|
|
1454
|
+
fail('real ' + harness + ' process changed the signed installation');
|
|
1455
|
+
}
|
|
1456
|
+
assertPinnedInstallationAuthority(root, installationBefore, installation);
|
|
1457
|
+
const executableAfter = digestProviderExecutable(
|
|
1458
|
+
executionExecutable.descriptor,
|
|
1459
|
+
executionExecutable.executable.snapshot,
|
|
1460
|
+
);
|
|
1461
|
+
if (executableAfter.sha256 !== pinnedExecutable.binding.executable_sha256) {
|
|
1462
|
+
fail('real ' + harness + ' executable drifted from its signed binding');
|
|
1463
|
+
}
|
|
1464
|
+
const supportingExecutableSha256 = executionExecutable.supporting_executables.map((support) => {
|
|
1465
|
+
const after = digestProviderExecutable(support.descriptor, support.executable.snapshot);
|
|
1466
|
+
if (after.sha256 !== support.binding.executable_sha256) {
|
|
1467
|
+
fail('real ' + harness + ' supporting executable drifted from its signed binding');
|
|
1468
|
+
}
|
|
1469
|
+
return after.sha256;
|
|
1470
|
+
});
|
|
1471
|
+
results.push({
|
|
1472
|
+
harness,
|
|
1473
|
+
provider_id: invocation.provider_id,
|
|
1474
|
+
model_id: invocation.model_pin.model_id,
|
|
1475
|
+
cli_model_id: nativeModelFor(harness, invocation.model_pin.model_id),
|
|
1476
|
+
reasoning_configuration: invocation.model_pin.reasoning_configuration,
|
|
1477
|
+
route_reason: E2E_ROUTE_REASON,
|
|
1478
|
+
permission_policy: permissionPolicyFor(harness),
|
|
1479
|
+
bound_executable_path: pinnedExecutable.binding.executable_path,
|
|
1480
|
+
sealed_executable_sha256: executableAfter.sha256,
|
|
1481
|
+
sealed_supporting_executable_sha256: supportingExecutableSha256,
|
|
1482
|
+
executable_sha256: executableAfter.sha256,
|
|
1483
|
+
cli_version: (versionResult.stdout || versionResult.stderr || '').trim().split('\n')[0],
|
|
1484
|
+
stdout_sha256: createHash('sha256').update(visibleOutput).digest('hex'),
|
|
1485
|
+
marker,
|
|
1486
|
+
visible: visibleSentence(language, harness),
|
|
1487
|
+
});
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
primaryError = error;
|
|
1490
|
+
throw error;
|
|
1491
|
+
} finally {
|
|
1492
|
+
if (sealedExecutable) {
|
|
1493
|
+
collectCleanupFailure(cleanupFailures, 'close-sealed-primary-descriptor', () => closeSync(sealedExecutable.descriptor));
|
|
1494
|
+
for (const support of sealedExecutable.supporting_executables) {
|
|
1495
|
+
collectCleanupFailure(cleanupFailures, 'close-sealed-supporting-descriptor', () => closeSync(support.descriptor));
|
|
1496
|
+
}
|
|
1497
|
+
collectCleanupFailure(cleanupFailures, 'remove-sealed-executable-directory', () => rmSync(sealedExecutable.directory, { recursive: true, force: true }));
|
|
1498
|
+
}
|
|
1499
|
+
if (pinnedExecutable) {
|
|
1500
|
+
collectCleanupFailure(cleanupFailures, 'close-pinned-primary-descriptor', () => closeSync(pinnedExecutable.descriptor));
|
|
1501
|
+
}
|
|
1502
|
+
for (const support of pinnedSupportingExecutables) {
|
|
1503
|
+
collectCleanupFailure(cleanupFailures, 'close-pinned-supporting-descriptor', () => closeSync(support.descriptor));
|
|
1504
|
+
}
|
|
1505
|
+
if (codexOutputDir) {
|
|
1506
|
+
collectCleanupFailure(cleanupFailures, 'remove-codex-final-message-directory', () => rmSync(codexOutputDir, { recursive: true, force: true }));
|
|
1507
|
+
}
|
|
1508
|
+
if (primaryError) recordCleanupFailure(primaryError, cleanupFailures);
|
|
1509
|
+
}
|
|
1510
|
+
throwCleanupFailures(cleanupFailures);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1514
|
+
runtimeProtection.assertIntact();
|
|
1515
|
+
assertPinnedInstallationAuthority(root, installationBefore, installation);
|
|
1516
|
+
readPinnedProjectFile(projectDescriptor, routerPath, 4 * 1024 * 1024, 'installed runtime router');
|
|
1517
|
+
const exitResult = runProtectedRouterExit(projectDescriptor, runtimeProtection.runtimeDescriptor, projectRoot);
|
|
1518
|
+
if (exitResult.error || exitResult.status !== 0) {
|
|
1519
|
+
fail('protected router exit failed', {
|
|
1520
|
+
status: exitResult.status, signal: exitResult.signal, error: exitResult.error?.message,
|
|
1521
|
+
output: ((exitResult.stdout || '') + '\n' + (exitResult.stderr || '')).slice(-20_000),
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
const exited = JSON.parse(exitResult.stdout);
|
|
1525
|
+
if (exited.language !== language) fail('exit changed the session language');
|
|
1526
|
+
if (exited.exited !== true || exited.session_saved !== true || exited.lock_removed !== true) {
|
|
1527
|
+
fail('exit did not durably deactivate every native lock', { exited });
|
|
1528
|
+
}
|
|
1529
|
+
const exitedSession = yaml.load(readPinnedProjectFile(
|
|
1530
|
+
projectDescriptor, '.chati/session.yaml', 4 * 1024 * 1024, 'canonical session',
|
|
1531
|
+
).toString('utf8'));
|
|
1532
|
+
if (exitedSession.language !== language || exitedSession.session_status !== 'paused') {
|
|
1533
|
+
fail('exit did not pause the canonical session without changing language', {
|
|
1534
|
+
language: exitedSession.language,
|
|
1535
|
+
session_status: exitedSession.session_status,
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
assertPinnedProjectDirectory(root, projectDescriptor);
|
|
1539
|
+
validateNativeLocks(root, { active: false, projectDescriptor });
|
|
1540
|
+
assertPinnedInstallationAuthority(root, installationBefore, installation);
|
|
1541
|
+
return Object.freeze({
|
|
1542
|
+
ok: true,
|
|
1543
|
+
language,
|
|
1544
|
+
sequence: results,
|
|
1545
|
+
...(releaseCandidate ? { release_candidate: releaseCandidate } : {}),
|
|
1546
|
+
exit: Object.freeze({ session_status: exitedSession.session_status, lock_removed: true }),
|
|
1547
|
+
resume_message: exited.resume_message,
|
|
1548
|
+
});
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
outerError = error;
|
|
1551
|
+
throw error;
|
|
1552
|
+
} finally {
|
|
1553
|
+
const cleanupFailures = [];
|
|
1554
|
+
if (runtimeProtection) collectCleanupFailure(cleanupFailures, 'restore-protected-installed-runtime', () => runtimeProtection.release());
|
|
1555
|
+
collectCleanupFailure(cleanupFailures, 'close-real-harness-project-directory-descriptor', () => closeSync(projectDescriptor));
|
|
1556
|
+
if (outerError) recordCleanupFailure(outerError, cleanupFailures);
|
|
1557
|
+
else throwCleanupFailures(cleanupFailures);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function main() {
|
|
1562
|
+
const projectDir = value(process.argv, '--project-dir');
|
|
1563
|
+
const language = value(process.argv, '--language');
|
|
1564
|
+
const claudeModel = value(process.argv, '--claude-model');
|
|
1565
|
+
const tarballSha256 = value(process.argv, '--tarball-sha256');
|
|
1566
|
+
const packageVersion = value(process.argv, '--package-version');
|
|
1567
|
+
const harnessesValue = value(process.argv, '--harnesses');
|
|
1568
|
+
const harnesses = harnessesValue ? harnessesValue.split(',').map((item) => item.trim()) : undefined;
|
|
1569
|
+
if (!projectDir || !language || !claudeModel) fail('usage: verify-real-harness-e2e --project-dir <installed-project> --language <pt|fr> --claude-model <canonical-or-native-model>');
|
|
1570
|
+
process.stdout.write(JSON.stringify(runRealHarnessE2E({
|
|
1571
|
+
projectDir, language, claudeModel, ...(harnesses ? { harnesses } : {}),
|
|
1572
|
+
...(tarballSha256 || packageVersion ? { tarballSha256, packageVersion } : {}),
|
|
1573
|
+
}), null, 2) + '\n');
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
1577
|
+
try { main(); } catch (error) {
|
|
1578
|
+
process.stderr.write(JSON.stringify({ ok: false, error: error.message, details: error.details }, null, 2) + '\n');
|
|
1579
|
+
process.exitCode = 1;
|
|
1580
|
+
}
|
|
1581
|
+
}
|