Skip to content

fix(sandbox): allowlist exec environment; require explicit opt-in for unconfined Linux - #4617

Closed
simpleqt wants to merge 5 commits into
openai:mainfrom
simpleqt:sandbox-env-isolation
Closed

fix(sandbox): allowlist exec environment; require explicit opt-in for unconfined Linux#4617
simpleqt wants to merge 5 commits into
openai:mainfrom
simpleqt:sandbox-env-isolation

Conversation

@simpleqt

Copy link
Copy Markdown
Contributor

Two gaps between the documented environment isolation and the Unix-local
implementation:

  • Commands inherited the full host environment (os.environ.copy()), so a
    sandboxed command could read host credentials (OPENAI_API_KEY, AWS_*,
    GITHUB_TOKEN) with printenv. The exec context now inherits only an
    allowlist (PATH, locale, TZ, TERM, TMPDIR, CA locations) plus manifest
    environment entries; inherit_environment=True restores the old
    behavior for workflows that depend on it.
  • On Linux, _confined_exec_command returned commands unmodified (the
    sandbox-exec wrapper is macOS-only), so 'sandboxed' commands ran with
    no OS-level confinement while the docs recommended UnixLocalSandboxClient
    as the default local client on macOS or Linux. Creating a session on
    Linux now raises with guidance toward DockerSandboxClient unless
    allow_unconfined_linux=True is passed explicitly (with a runtime
    warning). macOS behavior is unchanged.

Docs updated accordingly (clients.md environment-isolation notes,
guide.md client selection wording).

… unconfined Linux

Two gaps between the documented environment isolation and the Unix-local
implementation:

- Commands inherited the full host environment (os.environ.copy()), so a
  sandboxed command could read host credentials (OPENAI_API_KEY, AWS_*,
  GITHUB_TOKEN) with printenv. The exec context now inherits only an
  allowlist (PATH, locale, TZ, TERM, TMPDIR, CA locations) plus manifest
  environment entries; inherit_environment=True restores the old
  behavior for workflows that depend on it.
- On Linux, _confined_exec_command returned commands unmodified (the
  sandbox-exec wrapper is macOS-only), so 'sandboxed' commands ran with
  no OS-level confinement while the docs recommended UnixLocalSandboxClient
  as the default local client on macOS or Linux. Creating a session on
  Linux now raises with guidance toward DockerSandboxClient unless
  allow_unconfined_linux=True is passed explicitly (with a runtime
  warning). macOS behavior is unchanged.

Docs updated accordingly (clients.md environment-isolation notes,
guide.md client selection wording).
Copilot AI lite review requested due to automatic review settings August 24, 2026 11:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14b4307609

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/sandbox/sandboxes/unix_local.py Outdated
Comment thread src/agents/sandbox/sandboxes/unix_local.py Outdated
Comment thread src/agents/sandbox/sandboxes/unix_local.py Outdated
…_INDEX_URL

Review follow-ups:

- The inherit_environment flag lived on the serializable session state,
  so a RunState payload deserialized with inherit_environment=true would
  silently re-enable full host-environment inheritance on resume. Both
  flags are now set from trusted client-constructor values on every
  create()/resume(); the serialized value is overwritten, never trusted.
- The Linux unconfined check only ran in create(); resume() now enforces
  the same opt-in (state.error op=create/resume, same guidance).
- PIP_INDEX_URL removed from the default env allowlist: private package
  indexes commonly embed credentials in the URL (user:pass@host).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1841aefb65

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/sandbox/sandboxes/unix_local.py
Review follow-up: create() accepted allow_unconfined_linux via per-call
options while resume() only honored the constructor flag, so a workflow
opting in through options could create and serialize a session but fail
to resume it under the same run config. Both flags
(allow_unconfined_linux, inherit_environment) are now constructor
arguments on UnixLocalSandboxClient only — the single trusted channel
that applies to create() and resume() alike; the options class documents
why they are deliberately not per-call options. Regression test resumes
with the same opted-in client.
@simpleqt

Copy link
Copy Markdown
Contributor Author

Good catch on the create/resume asymmetry — fixed by making both flags constructor-only on UnixLocalSandboxClient (the single trusted channel that covers create() and resume() alike). They are no longer per-call options, and the options class docstring documents why: per-call options do not flow through resume(), so accepting them there would recreate the same asymmetry. The regression test now creates, stops and resumes with the same opted-in client.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf95e16db7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/sandbox/sandboxes/unix_local.py Outdated
Review follow-up: inherit_environment lived on the serializable
UnixLocalSandboxSessionState, so RunState snapshots carried it under the
unbumped 1.17 schema — an older SDK reading the snapshot would ignore the
unknown field and resume with the old full-host-environment behavior. The
flag is now a runtime-only attribute on the session object, wired from the
trusted client constructor in create() and resume(); it never enters the
persisted state. Regression test asserts the serialized payload contains
no inherit_environment key.
@simpleqt

Copy link
Copy Markdown
Contributor Author

Took the second option — the flag is now runtime-only: UnixLocalSandboxSessionState no longer has the field, so nothing enters the persisted RunState shape and the 1.17 schema is untouched. The session object carries a _inherit_environment attribute wired from the trusted client constructor in both create() and resume(). A regression test asserts serialize_session_state() output contains no inherit_environment key, so an older SDK reading any snapshot can never be downgraded back to full host-env inheritance.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fa17d8c7b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/sandbox/sandboxes/unix_local.py
Comment thread docs/sandbox/clients.md
Add "create"/"resume" to OpName so the unconfined-Linux ConfigurationError
survives the mount-redaction boundary as a structured error with its
error_code and retryable classification intact; previously it collapsed
into the generic redacted RuntimeError for credential-bearing manifests.

Route every Unix-local example through a shared unix_local_client() helper
that exits with actionable guidance on Linux unless
AGENTS_ALLOW_UNCONFINED_LINUX=1 is set (Docker examples recommended there),
and note the opt-in in the README/docs snippets.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4667a571b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


sandbox_clients: list[SandboxClientProvider] = [
SandboxClientProvider("local", UnixLocalSandboxClient()),
SandboxClientProvider("local", unix_local_client()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Temporal worker startup independent of local opt-in

On Linux without AGENTS_ALLOW_UNCONFINED_LINUX=1, this unconditional unix_local_client() call raises SystemExit before the worker can register Daytona, E2B, or Docker providers. That blocks the Temporal example even for its default daytona backend or a user selecting Docker, because startup never reaches the later provider-registration and warning logic. Treat the local provider like the other optional backends: only append it when the helper can build it, otherwise let the worker continue without local.

AGENTS.md reference: AGENTS.md:L157-L157

Useful? React with 👍 / 👎.

@seratch

seratch commented Aug 25, 2026

Copy link
Copy Markdown
Member

Thanks for sharing the idea. The allowlist for unix local client is a valid feature addition, so #4640 will add the change and its commit includes you as a co-author.

@seratch seratch closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants