10 KiB
Controller trust boundaries
Single-user-local update
single-user-local supports the same Windows SID for owner/controller. It uses a dedicated auth-only CODEX_HOME with a fresh sign-in to the same ChatGPT account; no tokens are copied. See single-user operation and its acceptance report. The older separate-owner requirements below apply to dedicated-runner, not to initializing the single-user mode.
The single-user worker uses an explicit MXC named permission profile: filesystem root denied, minimal trusted tools readable, exact worker/temp paths writable, control/auth/normal-Codex roots denied, Git metadata read-only, known production environment paths denied, command network disabled. Apps, plugins, remote plugins, MCP servers and web search are explicitly disabled. Neither legacy fallback nor unrestricted execution is allowed. Real canary probes cover workspace read/write, seven denied file categories, a listening host-loopback socket and temporary certificate/CNG signing. The exact configured owner key additionally needs a positive host signing control and negative worker probes before unattended mode.
Same-account isolation protects against the confined worker, not against arbitrary unrestricted processes already running as the owner. Such processes can alter owner files or invoke owner credentials. This does not provide the independent administrative boundary of a separate runner identity. A stopped operator terminal, native restrictions, protected state and signed scope are all still required. Failure, incomplete proof, cleanup failure, changed policy or expired qualification retains operator-controlled pause; it never substitutes writable approval JSON.
Threat model
Treat the worker model, repository content, test/build scripts, uploaded inputs and tool output as untrusted execution. Protect owner approvals, controller policy/code, durable state, test receipts, source identity, development publication and all production assets. Threats include forged PASS/approval JSON, replayed decisions, modified tests/artifacts, policy replacement, overlapping workers, branch/push manipulation, secret exposure and orphan processes.
The approval signature is implemented with .NET RSA PKCS#1/SHA256, using a pinned public certificate and an owner identity separate from the runner. The owner private key must never be exported or accessible from the worker. Signatures bind exact request bytes, manifest/controller/config hashes, source hashes via the manifest, baseline/candidate commit and evidence digests, expiry and decision. Revising/selecting a design is an explicit signed disposition. A JSON file saying approved, a model summary or a state checksum is not authority.
Required deployment boundary
- Use a dedicated non-administrator development account/VM without production credentials,
.env, secret mounts, SSH agent, Docker socket, host management APIs or routes to production/LAN services. The signing owner uses another account/machine.runnerSidmust match the live account and differ fromownerSid. Public-key trust is provisioned by the owner, not discovered from model output. - Install the trusted controller, immutable prompt/manifest snapshot, certificate/config and state outside the worker checkout. Native Codex workspace-write shell commands must not modify or read private control-plane records or owner material. Auth uses a dedicated minimal CODEX_HOME. Do not run the repository's mutable controller copy as the unattended trust authority.
- Qualify real filesystem/read/network/process restrictions and resource limits in the actual runner. The built-in negative write probe is one preflight check, not a sandbox certification. Windows native workspace-write denies external writes; it does not by itself prove all external reads or privileged tool calls are safe. Record negative access attempts to owner key/state/config/Git metadata/production paths and denied egress, plus permitted synthetic build/test needs.
- External plugin/MCP tools require their own qualified host boundary and least permissions. Protected
trustedCodexOverridesallows only explicit MCP/plugin namespaces, passed as separate supported-carguments; it cannot select unrestricted sandbox/approval behavior. The defaults load no user config or plugins. A configured tool still cannot be assumed safe merely because its namespace is allowed. Hash/config changes invalidate policy-bound approvals. - Tests execute through
codex sandboxtoo. Never execute a repository-controlled build/test command directly as the trusted host publisher. If sandbox execution cannot access a required safe development fixture, stop and qualify an appropriate constrained setup; do not disable sandbox/network controls to get green output. - The trusted publisher controls Git serially, disables hooks, uses explicit non-production refs, never force-pushes and verifies remote HEAD. Provision server-side protected main/production refs and branch-limited development credentials. Use a separate clone so a worker does not share the human workspace's Git directory or hooks. All controllers for one Git common directory use the same OS-held lock.
The current task runs in an unrestricted assistant environment, but does not use that permission level for unattended workers. Real execution is initially disabled and additionally rejects the current unprovisioned identity/certificate/sandbox conditions. No approval key or unattended system task was installed. These operational boundaries require owner qualification and remain CONDITIONAL until then.
Durable state, tests and process safety
The store uses same-directory atomic replace, fsync, checksum and a last-verified backup. It preserves RUNNING before launch. A launch-intent marker precedes process creation; after spawn, cleanup protection covers PID-marker failures. Input is a local file, so a child refusing stdin cannot block timeout/pause supervision. stdout/stderr are drained concurrently in bounded chunks; byte and line budgets prevent unbounded logging allocations. Each session has a wall-clock budget; the host VM/job must enforce hard CPU/RAM/process constraints and parent-death behavior.
Windows workers launch suspended, are attached to a kill-on-close Job Object, then resume. The Job imposes a configurable process-count and total-memory cap and kills contained descendants when the controller closes/dies. The delayed-assignment regression proves no worker code executes before attachment; the abrupt-parent-death test checks both child and grandchild termination. A crash before attachment can leave a suspended, non-executing process, so persistent RUNNING/launch markers still require signed owner reconciliation before relaunch. An old PID may have been reused. Do not blindly kill it. The operator must inspect the recorded tree/session and confirm it is gone. A lock prevents controller overlap; it cannot independently prove an old process stopped. No automatic deletion of a recovery marker occurs merely because time elapsed. CPU/whole-host limits and native sandbox enforcement still require operator qualification.
Test process exits, fresh structured results, expected identities, required artifacts and hashes are checked independently. A model's "complete" or "conditional" output is only a proposal. Missing adapters/scans/approvals block. Artifact tampering between verification/publication and approval blocks. A commit written just before a crash can be reconciled only when its exact controller transaction trailer, parent and durably prepared verification/content hashes match. Otherwise stop for operator recovery. A publication failure retries the same local commit without model rerun.
Final qualitative acceptance uses independent signed review of all source requirements. All seven stage completions remain development decisions. There is no code path for production deployment, main merge, destructive migration, paid service signup, risk suppression or production credential retrieval. This does not claim a sandbox can prevent every malicious Git/API command by itself; server and OS policy remain essential.
Secrets and evidence
Child environments inherit a small OS/path allowlist, CODEX_HOME and an orchestration marker. They do not inherit arbitrary API-key/production variables or sockets. Authentication material and protected logs must have owner-reviewed ACLs. Source snapshots are prompts and technical instructions, not private CVs or production documents.
Logs redact common password/token/API-key/Bearer/basic-URL patterns. Generic free text, encoded secrets and personal documents cannot be reliably scrubbed by regular expressions. Full stdin, result, structured test reports and session files are private local evidence, not public logs. Do not commit or send them externally. Notifications contain only stage IDs and bounded redacted status, never raw documents. Error output remains a reason to inspect private logs, not permission to paste them into a provider. The checked-in reports contain only synthetic test results and safe technical identities.
Back up control state, manifest, config, prompt versions, approvals, reports and Git checkpoint identities using the operator's approved encrypted backup policy. Do not put the signing private key into runner backups. Corruption recovery is an availability feature, not proof of tamper resistance; ACL/VM isolation protects against malicious writes.
Current limitations
- Native sandbox failed with
CryptUnprotectData failed: 2148073483; real shell isolation, plugin isolation and model execution were not qualified here. - Automated tests cover state transitions and .NET signature enforcement with disposable inputs. They do not qualify real hosted CI, provider licences, live models, physical devices, language accuracy, production infrastructure or hard host resource quotas.
- Runtime services, mandatory scan capabilities and stage-specific membership/qualification adapters must be installed and independently approved before their checkpoint can execute.
- Accepted stage policy cannot be rewritten in place. New scope/new stages require owner-reviewed migration or a separate queue. No automatic production stage exists.