Skip to content

Commit 9dc5038

Browse files
authored
Merge pull request #2292 from dyoshikawa/resolve-scrap-issue-2282-codex-danger-baseline
feat: support base_permission_profile :danger-full-access via default_permissions
2 parents 01aa622 + 7d95d85 commit 9dc5038

5 files changed

Lines changed: 192 additions & 21 deletions

File tree

docs/reference/file-formats.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -979,13 +979,13 @@ The `:workspace_roots` table also receives two default `.git` carve-outs: `".git
979979
980980
The carve-outs are skipped in three cases: a user rule for the same pattern always wins per key; the `codexcli.git_write_rules` override set to `false` suppresses them entirely (only an explicit `false` does; the default is `true`); and they are not injected when `codexcli.base_permission_profile` is `":read-only"` (they would grant `.git` write access inside a sandbox the user explicitly chose to keep read-only) or when the canonical rules contain a direct `":workspace_roots"` pattern (a whole-tree access decision that the defaults must not override). Like `:minimal`, the default-valued carve-outs are not imported into the Rulesync model on `rulesync import` — they are re-added on every generate — while customized `.git` values import normally. One limitation: the `git_write_rules` flag itself cannot be recovered from `config.toml`, so it does not round-trip through `rulesync import`; if you opted out with `false`, re-add the flag to the canonical permissions config after importing (and if you want the same `.git` rules while opted out, author them as canonical `read`/`write` rules, which round-trip normally, rather than hand-writing the exact default pairs in `config.toml`, which import skips).
981981
982-
The generated `[permissions.rulesync]` profile always extends one of Codex's built-in permission profiles via `extends`. The baseline is chosen with the `codexcli.base_permission_profile` override key (`":read-only"` | `":workspace"`) and defaults to `":workspace"` when unspecified. Codex's built-in `:workspace` baseline grants read access to the whole filesystem and write access to the entire workspace root plus `/tmp` and `$TMPDIR` (with carve-outs protecting `.git`, `.codex`, and `.agents`), while `:read-only` keeps command execution read-only; the generated `filesystem` entries then grant or deny access on top of the chosen baseline. Codex's third built-in profile, `:danger-full-access`, is rejected by `extends` at Codex config load time, so it is not an accepted value. On import, a profile's `extends` value round-trips back into `codexcli.base_permission_profile` when it names one of the two extendable built-ins; a custom parent profile is skipped and replaced by the managed baseline on regeneration (with a warning).
982+
The generated `[permissions.rulesync]` profile always extends one of Codex's built-in permission profiles via `extends`. The baseline is chosen with the `codexcli.base_permission_profile` override key (`":read-only"` | `":workspace"` | `":danger-full-access"`) and defaults to `":workspace"` when unspecified. Codex's built-in `:workspace` baseline grants read access to the whole filesystem and write access to the entire workspace root plus `/tmp` and `$TMPDIR` (with carve-outs protecting `.git`, `.codex`, and `.agents`), while `:read-only` keeps command execution read-only; the generated `filesystem` entries then grant or deny access on top of the chosen baseline. Codex's third built-in profile, `:danger-full-access`, is rejected by `extends` at Codex config load time — so selecting it works differently: Rulesync emits `default_permissions = ":danger-full-access"` directly and skips the managed `[permissions.rulesync]` profile entirely (with the sandbox removed there is nothing for filesystem/network rules to refine; canonical `read`/`edit`/`write`/`webfetch` rules are ignored for Codex CLI with a warning, and any stale managed profile from a previous generate is pruned while sibling hand-written profiles are preserved). On import, a profile's `extends` value round-trips back into `codexcli.base_permission_profile` when it names one of the two extendable built-ins, and a top-level `default_permissions = ":danger-full-access"` round-trips the same way; a custom parent profile is skipped and replaced by the managed baseline on regeneration (with a warning).
983983
984984
Rulesync always emits `":minimal" = "read"` in the generated filesystem table. This enables `include_platform_defaults()` ([FileSystemSpecialPath::Minimal](https://github.com/openai/codex/pull/13434)), which provides the platform/runtime read access needed for basic sandboxed command execution on macOS, Linux, and Windows. `:minimal` is the only special path treated as a non-user-managed fixed baseline: it is always emitted and is never imported into Rulesync's own permission model. The other special paths `:root`, `:tmpdir`, and `:slash_tmp` are user-managed access rules that are imported into the Rulesync model and re-emitted from it like any ordinary filesystem entry (`:root = "deny"` becomes a read/edit deny, `:tmpdir = "write"` becomes an edit allow, and so on). Because they round-trip through `.rulesync/permissions.json` rather than relying on an existing `.codex/config.toml`, a restrictive value such as `:root = "deny"` survives a fresh-clone `rulesync generate` with no pre-existing Codex config.
985985
986986
`network.mode`, `network.unix_sockets`, and `description` have no equivalent in Rulesync's canonical permissions model and are not generated. If an existing `.codex/config.toml` already contains these fields on the `rulesync` profile, Rulesync preserves them on regeneration — as it does any other network key it does not model (e.g. `dangerously_allow_all_unix_sockets` or Codex's proxy keys), since network settings are user territory by design. `network.enabled` is only half-managed: Rulesync sets `enabled = true` itself when the canonical model contains an allow domain, but when a regeneration computes no `enabled` value, a user-authored `enabled` is preserved (with a warning) instead of being deleted — see the [FAQ](../faq.md#codex-cli-denies-ssh-agent-access-temp-dir-writes-or-reading-its-own-config-with-a-generated-permissions-profile) for the recommended user-managed entries. The preservation applies only when the existing profile carries no allow domain: an existing `enabled` next to allow domains is Rulesync's own managed output, so removing every webfetch allow rule from the canonical model removes `enabled` too (falling back to Codex's restricted default) instead of leaving an unscoped `enabled = true` behind. Note that `filesystem`, `network.domains`, and `extends` are always managed by Rulesync (`filesystem`/`network.domains` derived from `edit`/`write`/`webfetch` rules, `extends` from `codexcli.base_permission_profile`), so hand-authored values in those fields will be replaced on regeneration.
987987
988-
> **Codex CLI-only override (`codexcli` key):** Codex CLI's permission surface is richer than the canonical allow/ask/deny model — its approval workflow, permission-profile baseline, and per-app tool gating have no canonical category. Add a tool-scoped `codexcli` override to author them: except for `base_permission_profile`, its fields are written verbatim as **top-level `.codex/config.toml` keys** (the override wins per key; existing sibling keys the user set directly are preserved, and table values are shallow-merged) while the shared `permission` block keeps driving the managed `[permissions.rulesync]` profile and `default_permissions`. Supported keys: `base_permission_profile` (`:read-only` | `:workspace`, default `:workspace` — not a top-level key; it becomes the managed profile's `extends` baseline, see above), `approval_policy` (`untrusted` | `on-request` (legacy alias `on-failure`) | `never`, or a `{ granular = { … } }` table kept verbatim; defaults to `on-request` when neither the override nor the existing config sets it), `apps` (per-app tool gating — `apps.<id>.tools.<tool>.approval_mode` / `.enabled`, `apps.<id>.default_tools_approval_mode`), `approvals_reviewer` (`user` | `auto_review` (legacy alias `guardian_subagent`), or a table; defaults to `auto_review` when neither the override nor the existing config sets it), and `git_write_rules` (boolean, default `true` — like `base_permission_profile` it is not a top-level key: it controls whether the managed profile's `:workspace_roots` table emits the default `.git` carve-outs described above; only an explicit `false` suppresses them). **Deprecated:** `sandbox_mode` (`read-only` | `workspace-write` | `danger-full-access`) with the sibling `sandbox_workspace_write` table (`network_access`, `writable_roots`, …) belong to Codex's classic sandbox system, which permission profiles supersede — Codex prioritizes these legacy keys over permission profiles when both are present, so authoring them disables the generated `[permissions.rulesync]` profile; they are still accepted (with a warning) so existing configs round-trip, but use `base_permission_profile` and the shared `permission` block instead. On import, the top-level keys round-trip back into the `codexcli` override, and the managed profile's `extends` round-trips into `base_permission_profile`. It is a `looseObject`, so future top-level Codex config keys can be authored here (merged verbatim on generate; only the listed keys are re-extracted on import). Example: `{ "permission": { … }, "codexcli": { "base_permission_profile": ":workspace", "approval_policy": "on-request", "approvals_reviewer": "auto_review" } }`. **Out of scope:** `mcp_servers.*` per-MCP gating is **not** authorable here — it is owned by the MCP feature (`codexcli-mcp.ts` writes the `mcp_servers` tables in the same `config.toml`), and `permissions` / `default_permissions` are owned by the canonical model; any such key placed in the override is skipped with a warning. See the [Codex configuration reference](https://developers.openai.com/codex/config-reference) and [permissions docs](https://developers.openai.com/codex/permissions).
988+
> **Codex CLI-only override (`codexcli` key):** Codex CLI's permission surface is richer than the canonical allow/ask/deny model — its approval workflow, permission-profile baseline, and per-app tool gating have no canonical category. Add a tool-scoped `codexcli` override to author them: except for `base_permission_profile`, its fields are written verbatim as **top-level `.codex/config.toml` keys** (the override wins per key; existing sibling keys the user set directly are preserved, and table values are shallow-merged) while the shared `permission` block keeps driving the managed `[permissions.rulesync]` profile and `default_permissions`. Supported keys: `base_permission_profile` (`:read-only` | `:workspace` | `:danger-full-access`, default `:workspace` — not a top-level key; it becomes the managed profile's `extends` baseline, or with `:danger-full-access` the directly-selected `default_permissions` value, see above), `approval_policy` (`untrusted` | `on-request` (legacy alias `on-failure`) | `never`, or a `{ granular = { … } }` table kept verbatim; defaults to `on-request` when neither the override nor the existing config sets it), `apps` (per-app tool gating — `apps.<id>.tools.<tool>.approval_mode` / `.enabled`, `apps.<id>.default_tools_approval_mode`), `approvals_reviewer` (`user` | `auto_review` (legacy alias `guardian_subagent`), or a table; defaults to `auto_review` when neither the override nor the existing config sets it), and `git_write_rules` (boolean, default `true` — like `base_permission_profile` it is not a top-level key: it controls whether the managed profile's `:workspace_roots` table emits the default `.git` carve-outs described above; only an explicit `false` suppresses them). **Deprecated:** `sandbox_mode` (`read-only` | `workspace-write` | `danger-full-access`) with the sibling `sandbox_workspace_write` table (`network_access`, `writable_roots`, …) belong to Codex's classic sandbox system, which permission profiles supersede — Codex prioritizes these legacy keys over permission profiles when both are present, so authoring them disables the generated `[permissions.rulesync]` profile; they are still accepted (with a warning) so existing configs round-trip, but use `base_permission_profile` and the shared `permission` block instead. On import, the top-level keys round-trip back into the `codexcli` override, and the managed profile's `extends` round-trips into `base_permission_profile`. It is a `looseObject`, so future top-level Codex config keys can be authored here (merged verbatim on generate; only the listed keys are re-extracted on import). Example: `{ "permission": { … }, "codexcli": { "base_permission_profile": ":workspace", "approval_policy": "on-request", "approvals_reviewer": "auto_review" } }`. **Out of scope:** `mcp_servers.*` per-MCP gating is **not** authorable here — it is owned by the MCP feature (`codexcli-mcp.ts` writes the `mcp_servers` tables in the same `config.toml`), and `permissions` / `default_permissions` are owned by the canonical model; any such key placed in the override is skipped with a warning. See the [Codex configuration reference](https://developers.openai.com/codex/config-reference) and [permissions docs](https://developers.openai.com/codex/permissions).
989989
990990
For Kiro, this generates tool permission settings in `.kiro/agents/default.json` (project mode):
991991

0 commit comments

Comments
 (0)