Skip to content

Commit b9dbb11

Browse files
committed
Add explicit danger-full-access example flag
1 parent 9761564 commit b9dbb11

4 files changed

Lines changed: 157 additions & 18 deletions

File tree

examples/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ applies to Codex CLI subprocesses and MCP HTTP/OAuth flows.
3535
./examples/run_all.sh
3636
./examples/run_all.sh --ssh-host example.internal
3737
./examples/run_all.sh --ssh-host example.internal --cwd /srv/trusted/repo
38+
./examples/run_all.sh --ssh-host example.internal --danger-full-access
3839
./examples/run_all.sh --ssh-host builder@example.internal --ssh-port 2222
3940
```
4041

@@ -62,6 +63,7 @@ their existing local default when you omit the flag.
6263
Supported SSH flags for CLI/app-server examples:
6364

6465
- `--cwd <path>`
66+
- `--danger-full-access`
6567
- `--ssh-host <host>` or `--ssh-host <user>@<host>`
6668
- `--ssh-user <user>`
6769
- `--ssh-port <port>`
@@ -73,6 +75,13 @@ those upstream surfaces do not expose `--skip-git-repo-check`. Point it at a
7375
trusted directory on the remote host when you want those examples to run over
7476
`execution_surface: :ssh_exec`.
7577

78+
`--danger-full-access` keeps the same transport placement and switches only the
79+
Codex runtime sandbox mode to `:danger_full_access`. This is the explicit
80+
example-level escape hatch for remote Linux hosts where sandboxed shell tool
81+
execution fails before the command runs, for example when the host's userns or
82+
AppArmor policy blocks the `bwrap` path that the remote Codex CLI is trying to
83+
use.
84+
7685
`--ssh-host` is mutually exclusive with `--ollama`, because `--ollama` is the
7786
local OSS route and `--ssh-host` is remote subprocess placement.
7887

@@ -142,6 +151,7 @@ SSH usage for CLI/app-server examples is explicit:
142151

143152
```bash
144153
mix run examples/live_cli_demo.exs -- --ssh-host example.internal "What is the capital of France?"
154+
mix run examples/live_cli_demo.exs -- --ssh-host example.internal --danger-full-access "Run the shell command ls and then say done."
145155
mix run examples/live_app_server_basic.exs -- --ssh-host builder@example.internal --ssh-port 2222 --cwd /srv/trusted/repo "Reply with exactly ok and nothing else."
146156
mix run examples/live_cli_session.exs -- --ssh-host example.internal --cwd /srv/trusted/repo "Summarize this repository in three bullets."
147157
```

examples/run_all.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ cd "$ROOT"
66

77
usage() {
88
cat <<'EOF'
9-
Usage: bash examples/run_all.sh [--ollama] [--ollama-model MODEL] [--cwd PATH] [--ssh-host HOST] [--ssh-user USER] [--ssh-port PORT] [--ssh-identity-file PATH] [--help]
9+
Usage: bash examples/run_all.sh [--ollama] [--ollama-model MODEL] [--cwd PATH] [--danger-full-access] [--ssh-host HOST] [--ssh-user USER] [--ssh-port PORT] [--ssh-identity-file PATH] [--help]
1010
1111
Options:
1212
--ollama Run all examples against local Codex OSS + Ollama.
1313
--ollama-model MODEL Override the Ollama model. Default: gpt-oss:20b
1414
--cwd PATH Working directory override. In SSH mode, app-server thread demos require a trusted remote cwd.
15+
--danger-full-access Run CLI/app-server examples with sandbox=danger_full_access.
1516
--ssh-host HOST Run CLI/app-server examples over execution_surface=:ssh_exec.
1617
--ssh-user USER Optional SSH user override.
1718
--ssh-port PORT Optional SSH port override.
@@ -58,6 +59,10 @@ while [[ $# -gt 0 ]]; do
5859
cwd_configured=true
5960
shift
6061
;;
62+
--danger-full-access)
63+
example_args+=("$1")
64+
shift
65+
;;
6166
--ssh-host|--ssh-user|--ssh-port|--ssh-identity-file)
6267
if [[ $# -lt 2 ]]; then
6368
echo "ERROR: $1 requires a value." >&2
@@ -167,6 +172,9 @@ else
167172
else
168173
echo "CLI model: shared core default"
169174
fi
175+
if [[ " ${example_args[*]} " == *" --danger-full-access "* ]]; then
176+
echo "CLI/App-server sandbox override: danger_full_access"
177+
fi
170178
EXAMPLE_TIMEOUT_SECONDS="${CODEX_EXAMPLES_TIMEOUT_SECONDS:-}"
171179
echo
172180
fi

lib/codex/examples_support.ex

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ defmodule Codex.ExamplesSupport do
1515
defstruct argv: [],
1616
execution_surface: nil,
1717
example_cwd: nil,
18+
example_danger_full_access: false,
1819
ssh_host: nil,
1920
ssh_user: nil,
2021
ssh_port: nil,
@@ -24,6 +25,7 @@ defmodule Codex.ExamplesSupport do
2425
argv: [String.t()],
2526
execution_surface: ExecutionSurface.t() | nil,
2627
example_cwd: String.t() | nil,
28+
example_danger_full_access: boolean(),
2729
ssh_host: String.t() | nil,
2830
ssh_user: String.t() | nil,
2931
ssh_port: pos_integer() | nil,
@@ -34,6 +36,7 @@ defmodule Codex.ExamplesSupport do
3436
@context_key {__MODULE__, :ssh_context}
3537
@ssh_switches [
3638
cwd: :string,
39+
danger_full_access: :boolean,
3740
ssh_host: :string,
3841
ssh_identity_file: :string,
3942
ssh_port: :integer,
@@ -110,21 +113,18 @@ defmodule Codex.ExamplesSupport do
110113
@spec ssh_enabled?() :: boolean()
111114
def ssh_enabled?, do: match?(%SSHContext{execution_surface: %ExecutionSurface{}}, context())
112115

116+
@spec danger_full_access?() :: boolean()
117+
def danger_full_access?, do: context().example_danger_full_access == true
118+
113119
@spec execution_surface() :: ExecutionSurface.t() | nil
114120
def execution_surface, do: context().execution_surface
115121

116122
@spec command_opts(keyword()) :: keyword()
117123
def command_opts(opts \\ []) when is_list(opts) do
118-
opts =
119-
case execution_surface() do
120-
%ExecutionSurface{} = surface -> Keyword.put(opts, :execution_surface, surface)
121-
nil -> opts
122-
end
123-
124-
case example_working_directory() do
125-
cwd when is_binary(cwd) and cwd != "" -> Keyword.put_new(opts, :cwd, cwd)
126-
_ -> opts
127-
end
124+
opts
125+
|> maybe_put_command_execution_surface()
126+
|> maybe_put_command_working_directory()
127+
|> maybe_put_command_danger_full_access()
128128
end
129129

130130
@spec example_working_directory() :: String.t() | nil
@@ -187,6 +187,7 @@ defmodule Codex.ExamplesSupport do
187187
attrs
188188
|> maybe_put_example_working_directory()
189189
|> maybe_put_skip_git_repo_check()
190+
|> maybe_put_thread_danger_full_access()
190191
|> ThreadOptions.new()
191192
end
192193

@@ -309,29 +310,67 @@ defmodule Codex.ExamplesSupport do
309310

310311
defp build_context(parsed, argv) do
311312
example_cwd = Keyword.get(parsed, :cwd)
313+
example_danger_full_access = Keyword.get(parsed, :danger_full_access, false)
312314
ssh_host = Keyword.get(parsed, :ssh_host)
313315
ssh_user = Keyword.get(parsed, :ssh_user)
314316
ssh_port = Keyword.get(parsed, :ssh_port)
315317
ssh_identity_file = Keyword.get(parsed, :ssh_identity_file)
316318

317319
case normalize_example_cwd(example_cwd) do
318320
{:ok, example_cwd} ->
319-
do_build_context(argv, example_cwd, ssh_host, ssh_user, ssh_port, ssh_identity_file)
321+
do_build_context(
322+
argv,
323+
example_cwd,
324+
example_danger_full_access,
325+
ssh_host,
326+
ssh_user,
327+
ssh_port,
328+
ssh_identity_file
329+
)
320330

321331
{:error, _reason} = error ->
322332
error
323333
end
324334
end
325335

326-
defp do_build_context(argv, example_cwd, nil, ssh_user, ssh_port, ssh_identity_file) do
336+
defp do_build_context(
337+
argv,
338+
example_cwd,
339+
example_danger_full_access,
340+
nil,
341+
ssh_user,
342+
ssh_port,
343+
ssh_identity_file
344+
) do
327345
with :ok <- validate_ssh_flag_dependencies(nil, ssh_user, ssh_port, ssh_identity_file) do
328-
{:ok, %SSHContext{argv: argv, example_cwd: example_cwd}}
346+
{:ok,
347+
%SSHContext{
348+
argv: argv,
349+
example_cwd: example_cwd,
350+
example_danger_full_access: example_danger_full_access
351+
}}
329352
end
330353
end
331354

332-
defp do_build_context(argv, example_cwd, ssh_host, ssh_user, ssh_port, ssh_identity_file) do
355+
defp do_build_context(
356+
argv,
357+
example_cwd,
358+
example_danger_full_access,
359+
ssh_host,
360+
ssh_user,
361+
ssh_port,
362+
ssh_identity_file
363+
) do
333364
with :ok <- validate_ssh_flag_dependencies(ssh_host, ssh_user, ssh_port, ssh_identity_file) do
334-
build_ssh_context(argv, example_cwd, ssh_host, ssh_user, ssh_port, ssh_identity_file)
365+
build_ssh_context(
366+
argv,
367+
example_cwd,
368+
example_danger_full_access,
369+
ssh_host,
370+
ssh_user,
371+
ssh_port,
372+
ssh_identity_file
373+
)
335374
end
336375
end
337376

@@ -343,7 +382,15 @@ defmodule Codex.ExamplesSupport do
343382
end
344383
end
345384

346-
defp build_ssh_context(argv, example_cwd, ssh_host, ssh_user, ssh_port, ssh_identity_file) do
385+
defp build_ssh_context(
386+
argv,
387+
example_cwd,
388+
example_danger_full_access,
389+
ssh_host,
390+
ssh_user,
391+
ssh_port,
392+
ssh_identity_file
393+
) do
347394
with {:ok, {destination, parsed_user}} <- split_host(ssh_host),
348395
{:ok, effective_user} <- coalesce_user(parsed_user, ssh_user),
349396
{:ok, identity_file} <- normalize_identity_file(ssh_identity_file),
@@ -362,6 +409,7 @@ defmodule Codex.ExamplesSupport do
362409
argv: argv,
363410
execution_surface: execution_surface,
364411
example_cwd: example_cwd,
412+
example_danger_full_access: example_danger_full_access,
365413
ssh_host: destination,
366414
ssh_user: effective_user,
367415
ssh_port: ssh_port,
@@ -432,6 +480,15 @@ defmodule Codex.ExamplesSupport do
432480
end
433481
end
434482

483+
defp maybe_put_thread_danger_full_access(attrs) when is_map(attrs) do
484+
if danger_full_access?() and not present?(present_value?(attrs, :sandbox)) and
485+
present_value?(attrs, :dangerously_bypass_approvals_and_sandbox) != true do
486+
Map.put(attrs, :sandbox, :danger_full_access)
487+
else
488+
attrs
489+
end
490+
end
491+
435492
defp present_value?(attrs, key) when is_map(attrs) and is_atom(key) do
436493
case {Map.get(attrs, key), Map.get(attrs, Atom.to_string(key))} do
437494
{nil, nil} -> nil
@@ -515,9 +572,32 @@ defmodule Codex.ExamplesSupport do
515572
{name, value} -> "--#{name}=#{value}"
516573
end)
517574

518-
"invalid example flags: #{rendered}. Supported flags: --cwd, --ssh-host, --ssh-user, --ssh-port, --ssh-identity-file"
575+
"invalid example flags: #{rendered}. Supported flags: --cwd, --danger-full-access, --ssh-host, --ssh-user, --ssh-port, --ssh-identity-file"
519576
end
520577

521578
defp maybe_put_kw(opts, _key, nil), do: opts
522579
defp maybe_put_kw(opts, key, value), do: Keyword.put(opts, key, value)
580+
581+
defp maybe_put_command_execution_surface(opts) when is_list(opts) do
582+
case execution_surface() do
583+
%ExecutionSurface{} = surface -> Keyword.put(opts, :execution_surface, surface)
584+
nil -> opts
585+
end
586+
end
587+
588+
defp maybe_put_command_working_directory(opts) when is_list(opts) do
589+
case example_working_directory() do
590+
cwd when is_binary(cwd) and cwd != "" -> Keyword.put_new(opts, :cwd, cwd)
591+
_ -> opts
592+
end
593+
end
594+
595+
defp maybe_put_command_danger_full_access(opts) when is_list(opts) do
596+
if danger_full_access?() and not Keyword.has_key?(opts, :sandbox) and
597+
not Keyword.has_key?(opts, :dangerously_bypass_approvals_and_sandbox) do
598+
Keyword.put(opts, :sandbox, :danger_full_access)
599+
else
600+
opts
601+
end
602+
end
523603
end

test/codex/examples_support_test.exs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ defmodule Codex.ExamplesSupportTest do
4646
ExamplesSupport.parse_argv([
4747
"--cwd",
4848
"/srv/codex",
49+
"--danger-full-access",
4950
"--ssh-host",
5051
"builder@example.internal",
5152
"--ssh-port",
@@ -61,6 +62,7 @@ defmodule Codex.ExamplesSupportTest do
6162
assert context.execution_surface.transport_options[:port] == 2222
6263
assert context.execution_surface.transport_options[:identity_file] =~ "/tmp/id_ed25519"
6364
assert context.example_cwd == "/srv/codex"
65+
assert context.example_danger_full_access == true
6466
end
6567

6668
test "rejects orphan ssh flags without --ssh-host" do
@@ -89,6 +91,29 @@ defmodule Codex.ExamplesSupportTest do
8991
end
9092
end
9193

94+
describe "command_opts/1" do
95+
test "injects shared cwd and danger-full-access for command helpers" do
96+
assert {:ok, context} =
97+
ExamplesSupport.parse_argv([
98+
"--cwd",
99+
"/srv/codex",
100+
"--danger-full-access",
101+
"--ssh-host",
102+
"example.internal"
103+
])
104+
105+
Process.put({ExamplesSupport, :ssh_context}, context)
106+
107+
opts = ExamplesSupport.command_opts([])
108+
109+
assert opts[:cwd] == "/srv/codex"
110+
assert opts[:sandbox] == :danger_full_access
111+
assert opts[:execution_surface].surface_kind == :ssh_exec
112+
after
113+
Process.delete({ExamplesSupport, :ssh_context})
114+
end
115+
end
116+
92117
describe "thread_opts/1" do
93118
test "injects skip_git_repo_check in ssh mode" do
94119
assert {:ok, context} = ExamplesSupport.parse_argv(["--ssh-host", "example.internal"])
@@ -138,6 +163,22 @@ defmodule Codex.ExamplesSupportTest do
138163
after
139164
Process.delete({ExamplesSupport, :ssh_context})
140165
end
166+
167+
test "injects danger-full-access when requested for examples" do
168+
assert {:ok, context} =
169+
ExamplesSupport.parse_argv([
170+
"--danger-full-access",
171+
"--ssh-host",
172+
"example.internal"
173+
])
174+
175+
Process.put({ExamplesSupport, :ssh_context}, context)
176+
177+
assert {:ok, thread_opts} = ExamplesSupport.thread_opts(%{})
178+
assert thread_opts.sandbox == :danger_full_access
179+
after
180+
Process.delete({ExamplesSupport, :ssh_context})
181+
end
141182
end
142183

143184
describe "ensure_remote_working_directory/1" do

0 commit comments

Comments
 (0)