Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/reference/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -1391,7 +1391,7 @@ For Kimi Code, permissions are global-only and generate `[[permission.rules]]` e
>
> **Alias overlap:** Qwen's `Read` is a meta-tool that also covers grep/glob/list, so canonical `grep`/`glob` rules are emitted as their own `Grep(...)`/`Glob(...)` entries but overlap Qwen's `Read` category at runtime; and Qwen folds web search into `web_fetch`, so a canonical `websearch` rule (`WebSearch(...)`) may not correspond to a distinct Qwen tool. `tools.disabled` is a hard whole-tool disable (stronger than `deny`) and is only authorable via the override, not the canonical `deny`.

For Warp, this generates the command allow/deny regex lists in Warp's global user `settings.toml` (**global mode only** — Warp has no project-scoped permissions file). Since Warp promoted file-backed execution profiles to Stable (2026-07-28), the surface runtime enforcement actually reads is the `command_allowlist` / `command_denylist` arrays of the `default` record under `[agents.execution_profiles.<id>]`; rulesync merges the lists into that `default` profile **in place** whenever the collection exists, preserving every other profile key and every other profile ID. The legacy `agent_mode_command_execution_allowlist` / `agent_mode_command_execution_denylist` keys under `[agents.profiles]` are still written for un-migrated installs and old clients — but on a migrated install they are inert (Warp consumes them only once during its one-shot migration). When the `[agents.execution_profiles]` collection does not exist yet, rulesync deliberately does **not** create it: on such an un-migrated install the legacy keys are still live, and creating the collection would mark Warp's migration complete early and strand the user's other legacy settings. Note that rulesync manages only the `default` profile — if a different execution profile is active in Warp, the generated lists (including `deny` rules) are not enforced until the user switches back to `default`. The settings file path differs per platform: macOS `~/.warp/settings.toml`, Linux `~/.config/warp-terminal/settings.toml`, Windows `%LOCALAPPDATA%\warp\Warp\config\settings.toml`. Only the `bash` category maps (`allow` → allowlist, `deny` → denylist); Warp matches commands with **regular expressions**, so patterns are emitted verbatim — author canonical `bash` patterns as regexes when targeting Warp (mirrors Zed). Warp has no per-command `ask` list, so `ask` rules are dropped, and non-`bash` categories are skipped (with a warning when they carry `deny` rules). On import, the `default` execution profile's lists are preferred (falling back to the legacy keys when no collection exists), and a pattern present in both lists resolves to `deny` (Warp's denylist wins). Both blocks are merged into the existing `settings.toml`, preserving other Warp settings, and the file is never deleted. **rulesync owns the command lists** (it is the source of truth): they are replaced from the rulesync config on each `--global` generate, so a manually curated Warp allowlist/denylist not mirrored in `.rulesync/permissions.jsonc` is overwritten — keep command permissions in rulesync (run `rulesync import` first to capture an existing hand-curated list). MCP allow/deny is a separate Warp surface not modeled here. See the [Warp agent profiles & permissions docs](https://docs.warp.dev/agent-platform/capabilities/agent-profiles-permissions/).
For Warp, this generates the command allow/deny regex lists in Warp's global user `settings.toml` (**global mode only** — Warp has no project-scoped permissions file). Since Warp promoted file-backed execution profiles to Stable (2026-07-28), the surface runtime enforcement actually reads is the `command_allowlist` / `command_denylist` arrays of the `default` record under `[agents.execution_profiles.<id>]`; rulesync merges the lists into that `default` profile **in place** whenever the collection exists, preserving every other profile key and every other profile ID. The legacy `agent_mode_command_execution_allowlist` / `agent_mode_command_execution_denylist` keys under `[agents.profiles]` are still written for un-migrated installs and old clients — but on a migrated install they are inert (Warp consumes them only once during its one-shot migration). When the `[agents.execution_profiles]` collection does not exist yet, rulesync deliberately does **not** create it: on such an un-migrated install the legacy keys are still live, and creating the collection would mark Warp's migration complete early and strand the user's other legacy settings. Note that rulesync manages only the `default` profile — if a different execution profile is active in Warp, the generated lists (including `deny` rules) are not enforced until the user switches back to `default`. The settings file path differs per platform: macOS `~/.warp/settings.toml`, Linux `~/.config/warp-terminal/settings.toml`, Windows `%LOCALAPPDATA%\warp\Warp\config\settings.toml`. Only the `bash` category maps (`allow` → allowlist, `deny` → denylist); Warp matches commands with **regular expressions**, so patterns are emitted verbatim — author canonical `bash` patterns as regexes when targeting Warp (mirrors Zed). Warp has no per-command `ask` list, so `ask` rules are dropped, and non-`bash` categories are skipped (with a warning when they carry `deny` rules). Writing a `command_denylist` at all **replaces** Warp's built-in default denylist — which covers `rm`, `curl`, `wget`, `eval`, `ssh`, shells, and other risky command patterns — so rulesync warns whenever it emits a non-empty denylist; add canonical `deny` rules equivalent to the built-in patterns you want to keep (see the [Warp CLI permissions docs](https://docs.warp.dev/cli/permissions-and-profiles/)). On import, the `default` execution profile's lists are preferred (falling back to the legacy keys when no collection exists), and a pattern present in both lists resolves to `deny` (Warp's denylist wins). Both blocks are merged into the existing `settings.toml`, preserving other Warp settings, and the file is never deleted. **rulesync owns the command lists** (it is the source of truth): they are replaced from the rulesync config on each `--global` generate, so a manually curated Warp allowlist/denylist not mirrored in `.rulesync/permissions.jsonc` is overwritten — keep command permissions in rulesync (run `rulesync import` first to capture an existing hand-curated list). MCP allow/deny is a separate Warp surface not modeled here. See the [Warp agent profiles & permissions docs](https://docs.warp.dev/agent-platform/capabilities/agent-profiles-permissions/).

> **Warp-only override (`warp` key):** Warp's `[agents.profiles]` table also exposes file-read/read-only autonomy knobs that do not fit the per-command `allow`/`ask`/`deny` model — `agent_mode_coding_permissions` (`always_ask_before_reading` / `always_allow_reading` / `allow_reading_specific_files`), `agent_mode_coding_file_read_allowlist` (an array of paths the agent may read), and `agent_mode_execute_readonly_commands` (a boolean auto-executing read-only commands). Add a tool-scoped `warp` override to author them: its keys are merged into `[agents.profiles]` (the override wins) while the shared `permission` block keeps driving the command lists. On **import**, these keys are lifted from `settings.toml` into the `warp` override, so they round-trip faithfully instead of being dropped. These legacy autonomy keys are part of Warp's one-shot migration, so on a migrated install they are inert; their execution-profile counterparts are authored through the nested `warp.execution_profile` block instead — `read_files` / `apply_code_diffs` / `execute_commands` / `mcp_permissions` (each `agent_decides` / `always_allow` / `always_ask`), `write_to_pty` (`always_allow` / `always_ask` / `ask_on_first_write`), `ask_user_question` (`never` / `ask_except_in_auto_approve` / `always_ask`), `run_agents` (`never_allow` / `always_allow` / `always_ask`), `computer_use` (`never` / `always_ask` / `always_allow`), `directory_allowlist` (paths readable without approval), and `mcp_allowlist` / `mcp_denylist` (MCP server IDs). Its keys are merged into the `default` record of `[agents.execution_profiles.<id>]` under the same guard as the command lists (only when the collection already exists — creating it would complete Warp's migration early; a warning is logged and the block skipped on an un-migrated install), unknown keys pass through verbatim for forward compatibility (export-only: import lifts back exactly the permission keys listed above, while profile-management keys such as `name` or the model overrides never round-trip), and the rulesync-owned `command_allowlist`/`command_denylist` always win. Example:
>
Expand Down
34 changes: 34 additions & 0 deletions src/features/permissions/warp-permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,40 @@ describe("WarpPermissions", () => {
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("code-review"));
});

it("warns that a written denylist replaces Warp's built-in default denylist", async () => {
const logger = createMockLogger();

await WarpPermissions.fromRulesyncPermissions({
outputRoot: testDir,
rulesyncPermissions: rulesyncPermissions({ bash: { "rm -rf .*": "deny" } }),
logger,
global: true,
});

expect(
logger.warn.mock.calls.some(([message]) =>
String(message).includes("replaces its built-in default denylist"),
),
).toBe(true);
});

it("does not warn about the built-in denylist when no deny rule is written", async () => {
const logger = createMockLogger();

await WarpPermissions.fromRulesyncPermissions({
outputRoot: testDir,
rulesyncPermissions: rulesyncPermissions({ bash: { "git .*": "allow" } }),
logger,
global: true,
});

expect(
logger.warn.mock.calls.some(([message]) =>
String(message).includes("replaces its built-in default denylist"),
),
).toBe(false);
});

it("merges the execution_profile override into the default profile on a migrated install", async () => {
const dir = join(testDir, WarpPermissions.getSettablePaths().relativeDirPath);
await ensureDir(dir);
Expand Down
13 changes: 12 additions & 1 deletion src/features/permissions/warp-permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ function warpSettingsDir(): string {
* `[agents.execution_profiles.<id>]` collection:
* - `command_allowlist` — commands that auto-execute.
* - `command_denylist` — commands that always require permission (the denylist
* wins over the allowlist).
* wins over the allowlist). Writing it at all replaces Warp's built-in
* default denylist, so rulesync warns whenever it emits a non-empty one.
* https://docs.warp.dev/cli/permissions-and-profiles/
*
* The legacy `[agents.profiles]` keys
* (`agent_mode_command_execution_allowlist` / `denylist`) are consumed only
Expand Down Expand Up @@ -251,6 +253,15 @@ export class WarpPermissions extends ToolPermissions {

agents.profiles = profiles;

if (mergedDeny.length > 0 && logger) {
logger.warn(
`Warp's command_denylist replaces its built-in default denylist, which covers rm, curl, ` +
`wget, eval, ssh, shells, and other risky command patterns. The ${mergedDeny.length} ` +
`deny rule(s) from .rulesync/permissions.jsonc are now the whole denylist — add ` +
`equivalents for the built-in patterns you want to keep.`,
);
}

mergeIntoDefaultExecutionProfile({
agents,
mergedAllow,
Expand Down
2 changes: 1 addition & 1 deletion src/generated/docs-content.ts

Large diffs are not rendered by default.

Loading