Overview

Keep high-risk operations behind an execution-layer gate, not a prompt

Safety: kxen's hard execution boundary#

Verdicts#

Every protected operation resolves to one of:

  • Allow: executes directly.
  • Ask: goes to Approval.
  • Deny: refused outright.
  • Recoverable: allowed, but recorded as a risk that can still be undone.

Deny outranks Ask, and Ask outranks Recoverable.

What gets hard-denied#

Safety refuses operations that can't be safely narrowed or whose consequences are unacceptable, including:

  • Disk formatting, partitioning, and system wipes.
  • System shutdown, restart, and breaking security configuration.
  • Destroying the Keychain, GPG private keys, or the credential root directory.
  • Wholesale destruction of databases, cloud projects, Kubernetes namespaces, or infrastructure.
  • Bulk deletion of Git refs.
  • Destructive file operations targeting system directories, the home root, or .git.
  • Any deletion target whose scope can't be statically determined because it contains unexpanded variables.

Recoverable deletion#

File tools and Session deletion use the system trash. trash inside Shell is recognized as a recoverable deletion. Recoverable never means you can delete system directories or .git.

Shell commands that would cause permanent deletion go straight to Deny — rm, rmdir, unlink, find -delete, and equivalent forms through absolute paths, command, env, xargs, or nested shell calls. When you need to delete a Workspace file, use the delete tool, which moves the target to the system trash.

Network boundary#

Web and remote-MCP URLs go through address checks. Loopback, link-local, CGNAT, and internal addresses are never treated as ordinary public targets.

Browser pins all of Chrome's HTTP, HTTPS, and WebSocket traffic through a controlled local proxy with no direct fallback configured. The proxy checks every address a single DNS resolution returns before connecting, and only connects using the already-checked SocketAddr; redirects, iframes, subresources, and page-script requests all pass through the same boundary. Restricted addresses, resolution ambiguity, limit overruns, timeouts, or the proxy exiting all fail closed. A post-action top-level URL re-check remains as an extra line of defense.

Operations that need your decision go through Approval.

Approval: get a human decision before high-risk execution#

Approval handles actions that have legitimate uses but shouldn't be left to the model's own judgment. It happens right before the tool actually executes.

Every Shell command gets its own approval#

Shell and exec-class tools force per-command Approval, including commands Safety judged harmless (Allow). There's no auto-approve, and no "remember this choice" switch — Approval is the only gate that lets a command through.

This is a deliberate fail-closed design: if the approval channel times out, gets interrupted, or is unavailable, the command doesn't run. However permissive the static rules are, they never substitute for a human confirming this specific command, this time.

Actions taken inside a Session show up in that Session's timeline. Workspace trust, provider credential imports, project stdio MCP, and other actions with no Session to attach to appear in the app-level persistent approval panel, so you can still allow or deny them after switching to Settings or Workspaces.

Relationship to a Safety verdict:

  • Deny never reaches Approval — your confirmation can't override it.
  • Ask, Allow, and Recoverable Shell commands all reach Approval, and you decide each one individually.
  • Non-Shell tools route by their Safety verdict: Allow executes directly, Ask goes to Approval.

What typically triggers it#

Shell already forces per-command approval on its own; Safety additionally flags these as typical Ask risks, and equivalent high-risk operations in non-Shell tools also reach Approval:

  • git push --force
  • A bare git reset --hard
  • sudo
  • git clean -f
  • kill -9
  • Package publishing and software uninstalls
  • Recursive permission changes

Operations Safety hard-denies never reach Approval, because your confirmation can't turn a Deny into an Allow.

What you see#

Approval shows the specific command or operation and the stated reason for it. Session-scoped decisions are recorded in that Session's timeline; global decisions have no Session to attach to, so kxen never fakes a timeline entry for them.

The frontend listens for live approval events and also recovers the broker's snapshot from approval.pending. A global recovery omits session_id; a Session-scoped recovery passes the exact session_id, so the same approval never shows up twice across two surfaces. A new Session returns its ID to the frontend first, then runs the session_start hook in the background, so the frontend can subscribe in time to recover that Session's Ask approvals.

Allowing only applies to that specific call — it never creates a permanent global allow rule. Any change to the command, target, or arguments requires a fresh decision.

BotRun Approval#

A BotRun's Approval is saved against that Run, and bound to the current revision, permission snapshot, and exact operation identity. A Bot-to-Bot Message, Task, or Routine trigger can never pre-consent to a side effect on the owner's behalf. An MCP connector must also satisfy both the Bot's durable Approval and that server's local policy at the same time — a Deny on either side can't be overridden by the other.

A collaboration flow that needs an owner decision before any side effect has started can form a workflow Approval without a fabricated operation identity. Once allowed, it still has to pass its actual execution boundary when the real tool call happens.

Cancellation and timeout#

Composer's "Stop" action takes priority over Approval. A call still waiting on approval is treated as denied once the Session is canceled — it never stays suspended, and never resumes execution in the background.

BotRun Cancel records a durable cancellation request first, then ends the wait. A late ordinary success or failure can't override the cancellation; a side effect that was already Started but whose outcome can't be confirmed moves into UNKNOWN and Bot Recovery.

Approval waits at most 5 minutes. An RPC client window that might be waiting on a global approval also includes the follow-up provider probe or MCP initialize time after approval, so it won't fail on an ordinary 30-second RPC timeout while the backend is still waiting or wrapping up. Independent approvals from multiple provider sources, project stdio servers, or an already-cached Workspace are all published together and waited on concurrently — each can still be allowed or denied separately, and it never chains into multiple 5-minute windows.

A dropped connection cancels any Approval still waiting on a decision — the operation doesn't execute. If Allow has already become the broker's only terminal state, the operation may already be in an irreversible or committing stage; that stage isn't force-cancelled by the dropped connection — it either finishes or rolls back through its own transaction. After reconnecting, you have to reconcile against real state — Session, Workspace, worktree, MCP status — rather than guessing the outcome from whether the last RPC response arrived. Seeing an approval request without having granted Allow never means the command already ran.

Updated

Was this page helpful?