Skip to content

Commit 7d8e29e

Browse files
fix(permissions): make yolo override auto mode (#1169)
1 parent bc62252 commit 7d8e29e

16 files changed

Lines changed: 344 additions & 78 deletions

‎docs/ARCHITECTURE.md‎

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

‎docs/IMPLEMENTATION.md‎

Lines changed: 21 additions & 21 deletions
Large diffs are not rendered by default.

‎docs/PRODUCT.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ $ corbits exec "Add JWT auth to the API"
6666
$ corbits run "Add JWT auth to the API"
6767
```
6868

69-
Same directors, tools, permissions, MCP, plugins, and hooks as the TUI — without the OpenTUI shell. Bootstrap shares `src/session/assemble-runtime.ts` with the TUI; see `docs/ARCHITECTURE.md` “Exec Runner” for intentional deltas (no workflow controller; single primary send; non-interactive permission gate; `ask_operator` unmounted when non-TTY). Compaction continuation matches TUI so long runs do not stall after compact. Streams assistant text to stdout for scripts and CI. Non-interactive by default: actions that need operator approval are denied unless `--dangerously-skip-permissions` is set, a persisted `/yolo` default is on, or auto mode covers them. `--dangerously-skip-permissions` still forces this process; secret-guard and authz still apply. `ask_operator` is not advertised on non-TTY exec; TTY exec still reads a single line from stdin.
69+
Same directors, tools, permissions, MCP, plugins, and hooks as the TUI — without the OpenTUI shell. Bootstrap shares `src/session/assemble-runtime.ts` with the TUI; see `docs/ARCHITECTURE.md` “Exec Runner” for intentional deltas (no workflow controller; single primary send; non-interactive permission gate; `ask_operator` unmounted when non-TTY). Compaction continuation matches TUI so long runs do not stall after compact. Streams assistant text to stdout for scripts and CI. Non-interactive by default: actions that need operator approval are denied unless `--dangerously-skip-permissions` or its `--yolo` alias is set, the active settings file has persisted yolo on, or auto mode covers them. Both CLI flags force skip-permissions for this process only; skip-permissions wins when combined with auto, so `--auto --yolo` runs in yolo mode. Secret-guard and authz still apply. `ask_operator` is not advertised on non-TTY exec; TTY exec still reads a single line from stdin.
7070

7171
Local multi-model capability checks use this path (`bun run eval:capability`); see `evals/capability/README.md`.
7272

@@ -92,20 +92,20 @@ the file path and parse details.
9292
## Safety Model
9393

9494
- **Tiered permission gate** — Read-only tools (`read_file`, `search_files`, `grep`, `list_dir`) run freely. Every consequential tool (`write_file`, `edit_file`, `run_shell`, …) is gated. The operator can Allow Once or Allow Always (scoped to a file, a directory, or a command shape). Allow Always applies for the rest of the session; Corbits also tries to remember it on disk so later sessions don't re-ask. If that write fails, the grant still holds this session and the operator is told remember did not stick.
95-
- **Secret guard** — Path-keyed tools (`read_file`, `write_file`, …) hard-deny sensitive files (`.env`, `id_rsa`, `*.pem`, `.aws/credentials`, `.ssh/*`, `.git-credentials`, and similar), even with approval, `--dangerously-skip-permissions`, or `/yolo`. Template files like `.env.example` are exempt. Shell commands that _reference_ those paths (e.g. `bun --env-file=.env.staging run …`, `cat .env`) require explicit operator approval and never auto-run in auto mode; once approved, they proceed. Tool-result scrubbing still redacts credential-shaped output that reaches the transcript.
95+
- **Secret guard** — Path-keyed tools (`read_file`, `write_file`, …) hard-deny sensitive files (`.env`, `id_rsa`, `*.pem`, `.aws/credentials`, `.ssh/*`, `.git-credentials`, and similar), even with approval, `--dangerously-skip-permissions`, `--yolo`, or `/yolo`. Template files like `.env.example` are exempt. In normal and auto modes, shell commands that _reference_ sensitive paths (e.g. `bun --env-file=.env.staging run …`, `cat .env`) require explicit operator approval. Yolo/skip-permissions modes allow those shell references without approval after catastrophic authorization checks; the path-keyed secret guard remains a hard deny. Tool-result scrubbing still redacts credential-shaped output that reaches the transcript.
9696
- **Catastrophic-command deny** — Destructive shell patterns that target system roots (`rm -rf /`, home, `/etc`, …), plus `mkfs`, `dd`, `sudo`, fork bombs, `curl | bash`, force-push, … are blocked before they run. Recursive delete of ordinary workspace paths is not hard-denied but requires operator approval (never auto in auto mode).
9797
- **Constrained auto mode** — Default is on (`auto = true`). Pass `--no-auto` to start in ask mode, or `--auto` to force it on; there is currently no in-session key to toggle it. Auto mode auto-approves workspace file writes/edits/deletes and unconstrained shell without per-action prompts, but it is not a free-for-all:
9898
- **Denied** (must use `write_file` / `edit_file`): shell file mutations via output redirection, `tee`, `sed -i` / `perl -i`, interpreter inline programs or heredocs.
9999
- **Still asks**: dependency installs and remote runners (npm/yarn/pnpm/bun, pip, cargo, go, brew, `npx`/`bunx`, …), recursive `rm`, force or uncontained git worktree add/remove/prune (contained non-force add/remove/prune and `list` auto-allow), shell that references sensitive paths, and opaque unparseable wrappers (variable expansion or command substitution).
100100
- **Wrapper peel**: `bash`/`sh`/`zsh -c`, `xargs`, and transparent prefixes (`env`, `nice`, `timeout`, …) are expanded so the same deny/ask rules see the inner payload.
101101
- Path-arg tools that escape the workspace are denied at authorize time (yolo still allows them). Writes under the in-workspace session state root still ask; mutating MCP and unknown tools still prompt. Shell that targets an outside path still asks.
102102

103-
- **Path sandboxing** — Tool path arguments are resolved against the working directory; paths that escape it are blocked unless `--dangerously-skip-permissions` / `/yolo` is on (secret-guard and authz hard denies still apply).
103+
- **Path sandboxing** — Tool path arguments are resolved against the working directory; paths that escape it are blocked unless `--dangerously-skip-permissions`, `--yolo`, or persisted `/yolo` is on (secret-guard and authz hard denies still apply).
104104
- **Write verification** — After every write/edit the file is re-read and compared to confirm the change actually landed; the result returned to the model (and shown to the operator) includes a bounded diff of the changed region — `write_file`, `edit_file`, `delete_file`, and each op inside `apply_patch` — so a follow-up `read_file` is never needed just to confirm an edit landed. A whole-file rewrite's diff is truncated (and says so) rather than blowing the result size cap.
105105

106106
## Slash Commands (TUI)
107107

108-
The TUI has an extensible slash-command framework. Built-ins include `/help` (shortcut + command overlay), `/model` (models-only picker for connected accounts; **Alt+A** or `/connect` adds a provider), `/settings`, `/permissions`, `/plugins`, `/clear`, `/new`, `/compact` (fold conversation context now, optional trailing instructions to the summarizer; does not wait for the 60% occupancy governor; idle success shows the fold and does not start a new turn), `/mcp` (enable, disable, or remove servers), `/handoff [optional instructions]` (folds context through the shared operator pipeline, then immediately starts the next turn with the instructions as the inbound content — default copy when omitted; unlike `/compact`, which stops after the fold, handoff always re-infers, so the operator can pivot goals without `/clear`; a handoff issued mid-tool-batch queues behind the in-flight batch and whichever boundary fires first runs the single fold), and `/yolo` (persists as the user-global skip-permissions default; `--dangerously-skip-permissions` still forces this process; secret-guard and authz still apply; `/yolo [on|off|toggle]`, bare `/yolo` toggles), plus a `/<name>` command per available workflow. When a session starts with the persisted default already on, the TUI shows a startup notice ("Permission prompts are disabled by your saved default…") so the silent machine-wide default is never invisible; `corbits exec` prints the equivalent warning to stderr. Plugins can register additional commands.
108+
The TUI has an extensible slash-command framework. Built-ins include `/help` (shortcut + command overlay), `/model` (models-only picker for connected accounts; **Alt+A** or `/connect` adds a provider), `/settings`, `/permissions`, `/plugins`, `/clear`, `/new`, `/compact` (fold conversation context now, optional trailing instructions to the summarizer; does not wait for the 60% occupancy governor; idle success shows the fold and does not start a new turn), `/mcp` (enable, disable, or remove servers), `/handoff [optional instructions]` (folds context through the shared operator pipeline, then immediately starts the next turn with the instructions as the inbound content — default copy when omitted; unlike `/compact`, which stops after the fold, handoff always re-infers, so the operator can pivot goals without `/clear`; a handoff issued mid-tool-batch queues behind the in-flight batch and whichever boundary fires first runs the single fold), and `/yolo` (`/yolo [on|off|toggle]`, bare `/yolo` toggles), plus a `/<name>` command per available workflow. `/yolo` persists skip-permissions to the active settings file. That file is the user-global `~/.corbits/settings.json` by default, making the setting machine-wide; explicit `--config <path>` selects a different active file, and `/yolo` writes that file. An ordinary TUI launch without the same `--config` returns to the user-global source and does not modify the custom file. `--dangerously-skip-permissions` and `--yolo` are process-only aliases. Secret-guard and authz still apply. When a session starts with its active persisted setting already on, the TUI and `corbits exec` warn that permission prompts are disabled by saved settings at the active settings path and direct the operator to edit that file to re-enable them. Plugins can register additional commands.
109109

110110
**Default skills** exist out of the gate as first-party slash **actions**, not director names: `/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`. Each one is a how-to playbook — the slash sends the skill body to the primary, which follows the steps. Skills do not assign identity or route the fleet; that stays on director system prompts. `/review` classifies the target first, then dispatches a selected fleet; `/pull-request-review` is worktree checkout plus a surface pass, loading `/review` for quality rules only; `/scribe` is how to maintain PRODUCT / ARCHITECTURE / IMPLEMENTATION; `/implement` is the per-commit review/build/critique loop — it does not steal planning from `/plan`. Substantial Builder work consumes a counsel / `/plan` plan first; tiny parent-DIY stays plan-optional. `/plan` authors an eng change plan (files, AC, non-goals, risks, ordered steps) and does not implement. `/create-issue` remains the tracker command: Linear MCP when available; otherwise it `ask_operator`s for the platform (GitHub etc.) and persists `Preferred issue tracker` in `.corbits/MEMORY.md` (GitHub via `gh issue create`). `/lexicon` owns director-prompt drift and size against the agents repo at a pinned commit. There is no first-party dispatch skill — Skywalker orchestrates natively. `git-rebase`, `linear-issue-workflow`, `style`, `philosophy`, `native-integration`, `typescript`, `ponytail`, and `opsh` stay `use_skill` only (`user-invocable: false`). Bake-only bars such as `idiot-proof` and `native-runtime` are not slashes and are not listed for `use_skill`. Draper and emil are not slashes; they remain closed directors via `spawn_agent(agent=…)`. There is no catch-all worker. Slash names are also available to the model via `skill_search` (descriptions) then `use_skill` (body). Disable the catalog in `/plugins` (`corbits-skills`) if you want them gone.
111111

@@ -133,7 +133,7 @@ The exact turn threshold is model-family-dependent (tighter for models with obse
133133

134134
**What the user sees:** In a non-interactive `corbits exec` run, a consequential action that needs approval returns a tool error explaining that approval is unavailable.
135135

136-
**Recovery:** Re-run interactively (TUI), pre-approve via persisted approvals, narrow the action, re-run with `--dangerously-skip-permissions`, or use `/yolo` in the TUI (persists as the user-global default).
136+
**Recovery:** Re-run interactively (TUI), pre-approve via persisted approvals, narrow the action, re-run with process-only `--dangerously-skip-permissions` / `--yolo`, or use `/yolo` in the TUI to persist to the active settings file.
137137

138138
### Resume after interruption
139139

@@ -145,7 +145,7 @@ line instead of a path dump.
145145

146146
## Configuration
147147

148-
Providers and models are configured in `~/.corbits/settings.json` (holds providers + credentials), with a selection-only per-repo `.corbits/settings.json` override. Select at launch with `--provider` / `--model`, or point at an alternate file with `--config <path>`. `--config` only overrides where provider _definitions_ come from; it composes with, rather than replaces, credentials for codex/xai OAuth-profile providers, which live in separate home-level auth stores (`~/.corbits/codex-auth.json`, `xai-auth.json`) and are merged into the catalog regardless of `--config`. Credentials are read only from these settings files and the OAuth auth stores — there is no environment-variable override and `.env` files are not loaded, so a stale or exported key can't shadow the configured provider. The agent is denied read access to both settings files.
148+
Providers and models are configured in the active settings file, which defaults to `~/.corbits/settings.json` (providers + credentials), with a selection-only per-repo `.corbits/settings.json` override. Select at launch with `--provider` / `--model`, or use `--config <path>` to select an alternate active file for provider definitions and TUI settings persistence such as `/yolo`. `--config` composes with, rather than replaces, credentials for codex/xai OAuth-profile providers, which live in separate home-level auth stores (`~/.corbits/codex-auth.json`, `xai-auth.json`) and are merged into the catalog regardless of `--config`. Credentials are read only from these settings files and the OAuth auth stores — there is no environment-variable override and `.env` files are not loaded, so a stale or exported key can't shadow the configured provider. Static secret-guard protection denies path-keyed read access to the default user-global `~/.corbits/settings.json` and per-repo `.corbits/settings.json`. An arbitrary active path selected with `--config <path>` is not added to that denylist at runtime.
149149

150150
## Optional Capabilities (plugins)
151151

‎src/config.test.ts‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { defined } from "../tests/helpers/defined.js";
22
import { afterEach, beforeEach, describe, test, expect } from "bun:test";
3-
import { mkdtemp, mkdir, writeFile, rm, readdir } from "node:fs/promises";
3+
import {
4+
mkdtemp,
5+
mkdir,
6+
readFile,
7+
writeFile,
8+
rm,
9+
readdir,
10+
} from "node:fs/promises";
411
import { tmpdir } from "node:os";
512
import { join, resolve } from "node:path";
613

@@ -1225,6 +1232,19 @@ describe("loadConfig", () => {
12251232
await expectCliHelp(["-h"]);
12261233
});
12271234

1235+
test("--help explains process-only yolo and its precedence over auto", () => {
1236+
expect(CLI_HELP_TEXT).toContain("--dangerously-skip-permissions, --yolo");
1237+
expect(CLI_HELP_TEXT).toContain("this process only (--yolo alias)");
1238+
expect(CLI_HELP_TEXT).toContain("--auto --yolo uses yolo mode");
1239+
expect(CLI_HELP_TEXT).toContain(
1240+
"/yolo in the TUI persists the active settings file",
1241+
);
1242+
expect(CLI_HELP_TEXT).toContain(
1243+
"the default settings file is machine-wide",
1244+
);
1245+
expect(CLI_HELP_TEXT).toContain("--config selects another source");
1246+
});
1247+
12281248
test("--help after flags throws CliHelpError", async () => {
12291249
await expectCliHelp(["--auto", "--help"]);
12301250
await expectCliHelp(["--auto", "-h"]);
@@ -1349,6 +1369,28 @@ describe("loadConfig", () => {
13491369
}
13501370
});
13511371

1372+
test("exec --auto --yolo enables process-only skip without changing settings", async () => {
1373+
const cwd = await emptyCwd();
1374+
try {
1375+
const globalPath = await writeGlobalSettings(cwd);
1376+
const settingsBefore = await readFile(globalPath);
1377+
const config = await loadConfig(
1378+
["exec", "--cwd", cwd, "--auto", "--yolo", "ship", "it"],
1379+
{ globalSettingsPath: globalPath },
1380+
);
1381+
1382+
assertConfigured(config);
1383+
expect(config.command).toBe("exec");
1384+
expect(config.task).toBe("ship it");
1385+
expect(config.auto).toBe(true);
1386+
expect(config.dangerouslySkipPermissions).toBe(true);
1387+
expect(config.skipPermissionsFromSettings).toBe(false);
1388+
expect(await readFile(globalPath)).toEqual(settingsBefore);
1389+
} finally {
1390+
await rm(cwd, { recursive: true, force: true });
1391+
}
1392+
});
1393+
13521394
test("seeds dangerouslySkipPermissions from global settings without the CLI flag", async () => {
13531395
const cwd = await emptyCwd();
13541396
try {

0 commit comments

Comments
 (0)