AIShell-Gate — Getting Started Guide

Getting Started Guide

Two programs that put a deterministic policy gate between shell commands and Unix execution — for AI agents and human operators alike.

aishell-gate-policy  ·  aishell-gate-exec  ·  aishell-gate-mcp

Copyright © 2026 AIShell Labs LLC Winston-Salem NC USA. All Rights Reserved. Use of this software requires a valid license. — www.aishellgate.com  ·  info@aishellgate.com

!! BETA RELEASE !! AIShell-Gate evaluates shell commands against a policy before they run. It is a risk-reduction tool — not a security guarantee and not a standalone security product — and provides no warranty of fitness for any particular purpose; see the LICENSE for full terms.

Do not use on production systems. Best tested on a clean, isolated OS instance. AIShell-Gate raises the cost and visibility of unsafe AI-generated commands; it does not guarantee total protection — see What the Policy Engine Will Not Do before any production use. Run new configurations with --dry-run first: it performs full policy evaluation and fires all confirmation gates but executes nothing.

./aishell-gate --policy-preset ops_safe --dry-run

Report issues to info@aishellgate.com.

00 What This Is

AIShell-Gate is two programs that sit between an AI agent — or a human operator — and the Unix shell. Every command is evaluated against policy before it can run, and every decision is audited.

AI Agent (or human operator) | JSON plan (or interactive command) v aishell-gate-exec no policy logic | subprocess call v aishell-gate-policy never executes | JSON decision v execve(validated argv) no shell; direct kernel call

aishell-gate-policy evaluates a command and returns a decision. It cannot execute anything — there is no execution code in this binary. aishell-gate-exec accepts a command or plan, asks the policy engine for a decision on every action, collects human confirmation where the decision requires it, then calls execve() directly. It contains no policy logic of its own.

That separation is the security property the whole system depends on. A compromised executor cannot grant itself permission to run something the policy engine has denied, because that decision happens in a different process it doesn't control.

The diagram above is the same for both populations the system serves. An AI agent submits a JSON plan; a human operator types a command at a terminal. Both arrive at the same policy engine, are judged by the same rules, and produce the same kind of decision. There is no separate human-mode configuration — whatever policy you write applies identically to both.

Five things the design insists on, always: default deny (no rule means denied), fail closed (parse errors and timeouts deny, never allow), no shell evaluation (execve() not system() — metacharacters are rejected, not interpreted), audit first (every decision is logged before any action happens), and explainable (every denial states which layer made the call, why, and what to do instead).

Worth knowing aishell-gate-policy's interactive mode — which you're about to try — is useful on its own, with no AI involved at all. For someone learning Unix, it explains the risk of every flag in plain English at the moment they type it. For an experienced operator, it's a disciplined, audited workflow applied to their own typing. Both are real reasons people run it.

aishell-gate

aishell-gate is the stable entry point — use it instead of calling aishell-gate-exec directly. Today it's a thin pre-flight wrapper around the executor; in v2 it becomes the multi-agent, multi-operator broker (session coordination, multi-agent routing, multi-operator confirmation). Because everything already flows through aishell-gate, that transition won't require changes to deployed SSH forced commands, MCP configurations, or your own muscle memory.

Before handing off, aishell-gate refuses to run as root, verifies both binaries are present, executable, and not setuid/setgid, then injects --policy-binary automatically and calls exec() to replace itself with the executor — it doesn't remain in the process tree.

aishell-gate [exec-flags] [policy-flags] [-- other-policy-flags]
aishell-gate --policy-preset ops_safe --audit-log ./exec.jsonl

aishell-gate looks for aishell-gate-exec and aishell-gate-policy in its own directory by default, so when all three files sit together — an unpacked beta tarball, or a system install under /usr/local/bin — it finds them automatically with no configuration. Set AISHELL_EXEC_BIN and AISHELL_POLICY_BIN only when the binaries live in a different directory than the aishell-gate script:

AISHELL_EXEC_BIN=/opt/aishell/aishell-gate-exec \
AISHELL_POLICY_BIN=/opt/aishell/aishell-gate-policy \
aishell-gate --policy-preset ops_safe

(In the examples through the rest of this guide, all three files are assumed to sit in the same directory, so these variables are omitted — set them only if your binaries live elsewhere.)

01 Editions

AIShell-Gate ships in two editions, Standard and Enterprise. Both share the same policy engine, execution gateway, all four confirmation levels, interactive mode, audit logging, and jail-root enforcement. Enterprise adds the CI/CD-oriented presets, base- and project-layer policy overrides, and a cryptographically tamper-evident audit chain (HMAC-SHA256, --audit-verify, --audit-key). Relay confirmation mode (--confirm-pipe, --confirm-lock, aishell-gate-confirm) for remote and multi-session deployments is available in both editions. See §08 for the full feature-by-feature split.

Check which you have:

./aishell-gate-policy --version
./aishell-gate-exec   --version

The output names the edition explicitly — e.g. aishell-gate-policy standard. An enterprise-only flag run on a standard binary prints a clear "not available in standard edition" message rather than failing silently or guessing. Full flag-by-flag detail is in the man pages; this is just enough to know what you're running.

02 Run Something Right Now

No configuration files, no setup. Run the policy engine with no arguments and type a command:

./aishell-gate-policy
aishell-gate-policy  [educational mode]
Preset: ops_safe       Default-deny: yes  Net-default-deny: yes
Source: human          User: alice  CWD: /home/alice

This tool assesses commands and explains every flag.
It does NOT execute. To execute, pipe through aishell-gate-exec.
────────────────────────────────────────────────

policy> git status

  ALLOW  git status
  Confirmation required: none  (no prompt needed)
  Flag analysis: all flags are known-safe.

  To execute through the gateway:
    echo 'git status' | aishell-gate-exec --source human

policy> ls -la

  ALLOW  ls -la
  Confirmation required: none  (no prompt needed)
  Flag analysis: all flags are known-safe.

  To execute through the gateway:
    echo 'ls -la' | aishell-gate-exec --source human

policy> rm -rf /var/log

  DENY   rm -rf /var/log
  Reason: rm -rf: recursive force delete denied
  Detail: -rf
  Fix:    Remove or replace the denied argument.

policy> quit

The default preset is ops_safe, a conservative set of read-only and repository inspection commands — you'll see this behaviour with no configuration files in place.

That's the whole loop: type a command, see the decision and the reasoning behind it. Nothing executes. Type json after any command to see the full decision record; quit or exit to leave.

A note on how this is supposed to feel The interactive policy engine takes no flags, needs no config files, and creates no files. Run the binary, type any command, learn by watching. Software that asks you to understand it before it lets you use it wastes the user's time — we have spent enough hours of our own lives on that kind of software to want the opposite here.

Why a Human Would Use This

The policy engine's flag catalog carries a documented reason for every flag that raises risk — not a generic label, a specific plain-English explanation:

An unknown flag is explicitly labelled "risk unassessed, not evaluated as safe" — the system never implies it checked something and found it safe when it didn't. For a junior operator, this is instruction delivered in context at the moment of the command. For an experienced operator, it's a disciplined workflow with confirmation gates and full audit trail on every command run.

Confirm the rest of what you have while you're here:

python3 bin/MCP/aishell-gate-mcp --version   # the MCP server lives in bin/MCP/

03 Every Way In — Pick Yours

The terminal session above is one of several ways to drive AIShell-Gate. Same policy engine, same audit chain, every time — only who's submitting the command changes.

InterfaceWho it's forWhere it's covered
Interactive policy engineAnyone learning the system or auditing a policy by hand. No AI, no execution, no config.§02 above
Interactive executorOperators who want the same evaluation and audit trail on commands that actually run.§04 below
JSON plan pipeAnything that can write a JSON plan to stdout — CI, test harnesses, hand-written files, custom agents.§04 below
Single-command checkCI pipelines, pre-commit hooks, regression suites — a policy decision with nothing executed.§04 below
curl / pipe / chatSeeing an AI model's output go through the gate, with progressively less typing.§05 below
MCP (Claude Code & Cursor)Developers in an AI coding environment. The AI builds plans; you supervise.Using MCP guide
Remote SSH agentAn autonomous AI agent on a different machine, with a human confirming from a separate terminal.Remote Deployment Guide
Only here for MCP? Everything you need is in the Using MCP guide — two config files and a restart. You can stop reading this guide now. Everyone else: keep going, the sections below build on each other.

04 Now Make It Actually Run

The policy engine alone never executes. To run something, pick a preset and pass a command (or a plan) through aishell-gate.

Presets

A preset is a named starting set of allowed and denied commands, selected with --policy-preset. If you're just starting: use ops_safe or dev_sandbox. The CI presets require specific runtime conditions (--jail-root, batch mode), Enterprise edition, and aren't meant for interactive use.

PresetDescription
read_onlyInspection commands only: ls, cat, grep, find, ps, df, git status, git log. Denies anything that writes, deletes, or escalates privilege.
dev_sandboxDeveloper workflow commands: git, make, compilers, package managers (pip, cargo, go, npm). Blocks disk destructors and privilege escalation.
ops_safeThe default. A small conservative set of read and repository commands. Denies shells, interpreters, most writes, and all privilege commands.
ci_build [ENTERPRISE]Unattended build/test pipeline. All allowed commands carry CONFIRM_NONE — prompts would hang a headless runner. Requires --jail-root.
ci_deploy [ENTERPRISE]Unattended deployment pipeline. Superset of ci_build adding remote transfer, containers, Kubernetes, Helm, Terraform, service restart. Destructive operations stay denied.
ci_admin [ENTERPRISE]CI/CD infrastructure management with mixed confirmation: reads at CONFIRM_NONE, writes at CONFIRM_ACTION, terraform destroy and shells at CONFIRM_TYPED.
danger_zone [ENTERPRISE]Minimal restrictions — a wildcard allow, though the built-in deny list (shells, interpreters, disk destructors, privilege escalation) still applies. All commands require typed confirmation.

Whatever preset you choose, confirmation levels and risk scoring still apply on top of it — a high-risk command doesn't bypass confirmation just because the preset allows it.

Interactive execution

Same typed-command feel as §02, but now approved commands actually run:

./aishell-gate --policy-preset ops_safe

Every command is evaluated, confirmed if the risk score requires it, logged, then executed via execve() — never through a shell.

A JSON plan

The plan format is simple: a goal, an optional strategy, and an array of commands. (Caller identity is set with the --source flag, not in the plan — any source field in the JSON is ignored.)

echo '{
  "protocol": {"name": "aishell-gate-exec-input", "version": "1.0"},
  "goal":     "check repository state",
  "strategy": "fail_fast",
  "actions": [
    {"cmd": "git pull"},
    {"cmd": "git status"},
    {"cmd": "npm test"}
  ]
}' | ./aishell-gate --policy-preset dev_sandbox

The executor submits each action to the policy engine in sequence, reads the JSON verdict back, collects confirmation if required, then calls execve() with the validated argument array from the verdict — the raw command string never touches a shell. A policy denial on any action refuses the entire plan before anything runs, regardless of strategy. The strategy field governs only what happens once execution has begun and a command exits non-zero: fail_fast stops at the first non-zero exit; best_effort attempts the remaining allowed, confirmed actions and reports the first failure.

Keep it simple: When aishell-gate, aishell-gate-exec, and aishell-gate-policy all live in the same directory and you run from it, no --policy-binary flag is ever needed — the executor locates the policy engine at ./aishell-gate-policy by default, and aishell-gate handles this automatically. You only need --policy-binary explicitly when binaries are installed elsewhere and you're calling aishell-gate-exec directly rather than via aishell-gate.

A single command, no execution

echo "git status" | ./aishell-gate-policy --policy-preset dev_sandbox --json

Returns a structured decision — allow/deny, confirmation level, risk score, the reasoning — without running anything. The exit code from aishell-gate-policy is always 0 when it runs without internal error, regardless of whether the decision was ALLOW or DENY — the decision lives in the JSON output, not the exit code, so the policy engine is never mistaken for a failed command in a pipeline. (Exit code 1 means internal error; 2 means a usage problem.)

FieldMeaning
decisionallow or deny
confirmRequired confirmation level: none, plan, action, or typed
layerPolicy layer that produced the decision
reasonHuman-readable explanation of why
risk.scoreInteger 0–100
risk.blast_radiussingle, tree, system, or unknown
argvValidated argument vector — passed to execve() on ALLOW
suggestionsAllowed alternatives — may be present on DENY decisions when alternatives exist

Every decision — interactive, executor, MCP, or pipe scripts — carries these same fields. This is the integration point for CI pipelines, pre-commit hooks, and regression suites: shell out to this with --json, parse the result, decide what to do next.

Already in the toolbox Because execution happens as a normal process, standard Unix observability applies without adaptation — ps, top, lsof, and strace see honest process trees. The audit log is a file and SSH logs to syslog, so both feed existing log pipelines with no adapters or agents needed. Nothing is hidden behind a proprietary protocol.
Also worth knowing aishell-gate can be called per-command from inside an existing shell script to add policy evaluation and an audit trail without rewriting the script's logic. It works, and is a reasonable migration path for legacy scripts — though a JSON plan is the interface the system is actually designed around.

05 See It Talk to a Model

Everything above used commands you typed. These three steps hand that job to an AI model instead — same gate, same policy, same audit log, increasingly less typing.

API key vs. subscription. The Anthropic API needs a separate API key from platform.anthropic.com with pay-per-token billing. A claude.ai subscription (including Max) does not provide API access.

1. curl — confirm your key works

The smallest possible check: one call to the model, no gate involved at all.

./aishell-gate-curl.sh

Prompts for ANTHROPIC_API_KEY if it isn't already set (input hidden), sends a one-line prompt, prints the model's reply. If this works, your key and network access are good and you're ready for the next step.

2. pipe — a model-generated plan, through the gate

Describe a goal in plain English. The script asks the model for a JSON plan, validates it, and pipes it through aishell-gate — all in one command:

# Dry run — policy evaluated, nothing executed
./aishell-gate-pipe.sh --dry-run "check disk usage and list recent logs"

# Live run
AISHELL_PRESET=dev_sandbox ./aishell-gate-pipe.sh "run the test suite"

Always try --dry-run first to see the generated plan before letting it execute. Pass --backend ollama to use a local model instead of the Anthropic API.

3. chat — a live conversation, gated

A conversational REPL: you talk, the model proposes commands, the gate enforces, output feeds back into the conversation.

python3 ./aishell-gate-chat.py

Locates the gate binaries automatically, prompts for your API key on first run and saves it, then opens a live session — no plan-writing, no config file, no markdown wrangling. It's the fastest way to see the full loop end to end: you describe what you want, the model proposes a command, the gate decides, you see the result, the conversation continues.

All three steps use the same ANTHROPIC_API_KEY and the same gate underneath. Once chat feels natural, everything in the rest of this guide — policy files, audit logs, jail roots — applies to it exactly the same way it applies to a plan you wrote by hand.

06 What This Is

This guide assumes familiarity with Unix command-line environments and basic JSON structure.

AIShell-Gate is two C programs that work together to put a deterministic policy gate between shell commands and Unix execution. It serves two distinct user populations from a single unified policy engine.

AI agents submit structured JSON execution plans. Every command in the plan is evaluated against policy before anything runs. The AI never receives direct shell access; it submits requests and receives decisions.

Human operators work interactively at a terminal. The same policy engine, the same confirmation gates, and the same audit chain apply to commands typed by a human as to commands proposed by an AI. For human operators the system functions as three things simultaneously: a safety net that catches dangerous commands before execution, a teaching tool that explains why each flag raises the risk profile it does, and a disciplined workflow that provides compliance-grade audit accountability for shell activity.

The two programs:

aishell-gate-policy is the policy engine. It receives a proposed command, normalizes it, evaluates it against the loaded policy stack, computes a risk score, and emits a structured JSON decision. It has no ability to execute anything. Its interactive mode is purely educational — it assesses commands and explains every flag with its documented reasoning. It never executes.

aishell-gate-exec is the execution gateway. It accepts a JSON plan from an AI agent, or reads commands typed interactively at a terminal, submits each through the policy engine, collects human confirmation where required, and calls execve() with the validated argument array. It contains no policy logic — it cannot approve or deny anything. Every execution decision is made by the policy engine in a separate process across a hard OS boundary.

That separation is the security property the system depends on. A compromised executor cannot grant itself permission to run a command the policy engine has denied.

AI agent path:

AI Agent JSON plan { goal, actions: [ {cmd: ...}, ... ] } aishell-gate-exec (no policy logic) aishell-gate-policy (policy engine, separate process) ALLOW / DENY + confirmation level + flag assessment execve() (validated argv only, no shell)

Human operator path:

Human operator command typed at terminal (or JSON plan file) aishell-gate-exec --interactive (reads from /dev/tty) aishell-gate-policy (same policy engine, same rules) ALLOW / DENY + confirmation gate + flag explanation execve() (validated argv only — or educational assessment only)

Both paths use the same policy file. There is no separate human-mode configuration.

07 aishell-gate

aishell-gate is the stable user-facing entry point to the entire system — present and future. Today it is a thin pre-flight wrapper around aishell-gate-exec. In v2 it becomes the multi-agent, multi-operator broker: session coordination, multi-agent routing, multi-operator confirmation. Because all invocations already flow through aishell-gate, the v2 transition will require no changes to deployed SSH forced commands, MCP configurations, or user muscle memory.

Use aishell-gate in preference to calling aishell-gate-exec directly. It runs pre-flight checks, injects --policy-binary automatically, and hands off to the executor via exec(). All flags pass through unchanged.

Synopsis:

aishell-gate [exec-flags] [policy-flags] [-- other-policy-flags]
aishell-gate --policy-preset ops_safe --audit-log ./exec.jsonl
echo '<json-plan>' | aishell-gate --policy-preset dev_sandbox

What it does before handing off:

Binary resolution. aishell-gate resolves aishell-gate-exec and aishell-gate-policy relative to its own directory — the location of the aishell-gate script itself — so co-located binaries are found automatically with no configuration. This works the same whether the three files sit in an unpacked beta directory or a system install under /usr/local/bin.

Environment overrides — set these only when the binaries live in a different directory than the script:

VariableDefaultPurpose
AISHELL_EXEC_BINaishell-gate-exec beside the scriptPath to aishell-gate-exec
AISHELL_POLICY_BINaishell-gate-policy beside the scriptPath to aishell-gate-policy

For example, if the binaries are installed in a different location:

AISHELL_EXEC_BIN=/opt/aishell/aishell-gate-exec \
AISHELL_POLICY_BIN=/opt/aishell/aishell-gate-policy \
aishell-gate --policy-preset ops_safe

08 Editions

AIShell-Gate is available in two editions: Standard and Enterprise. Both editions share the same policy engine, execution gateway, all four confirmation levels, interactive mode, audit logging, and jail-root enforcement. Enterprise adds the CI/CD-oriented presets, base- and project-layer policy overrides, and cryptographic audit chain integrity. Relay confirmation mode for remote and multi-session deployments is available in both editions.

To check which edition you have:

./aishell-gate-policy --version
./aishell-gate-exec   --version

The version output identifies the edition explicitly — for example: aishell-gate-policy standard or aishell-gate-policy enterprise.

FeatureStandardEnterprise
Policy evaluation engine
Execution gateway
Presets: read_only, dev_sandbox, ops_safe
Presets: ci_build, ci_deploy, ci_admin, danger_zone
Confirmation gates (none / plan / action / typed)
Single-session TTY confirmation (--confirm-tty)
Relay confirmation mode for remote/multi-session deployments (--confirm-pipe, --confirm-lock, aishell-gate-confirm)
Interactive mode (educational, never executes)
Audit logging (JSON Lines)
Jail-root path enforcement
Session policy gating
Custom policy files: user layer (--policy-user)
Custom policy files: base / project layers (--policy-base, --policy-project)
--dump-standard-template — export editable policy template
HMAC-SHA256 tamper-evident exec audit chain
--audit-verify — verify exec audit log chain integrity
--audit-key — keyed HMAC exec audit chain
Cryptographic session ID in audit and confirmation
Standard edition behaviour: If you invoke an enterprise-only flag on a standard binary, the binary emits a clear not available in standard edition message and exits cleanly — it does not silently ignore the flag or produce a generic unknown-option error.
Audit logging note Both editions write a JSON Lines audit log when --audit-log is supplied. The standard edition log records every decision and is append-only. The enterprise edition log additionally carries an HMAC-SHA256 chain — each entry is cryptographically linked to the previous one, sequence-numbered, and tied to a cryptographic session ID. Only the enterprise binary can verify its own chain with --audit-verify. Do not feed a standard-edition log to enterprise --audit-verify or vice versa.

Contact [email protected] or visit www.aishellgate.com to upgrade from standard to enterprise.

09 First Run: Policy Engine Interactive

AIShell-Gate has two interactive modes. This section covers the first — the policy engine's educational mode, which assesses commands and explains its reasoning but never executes. The second — the executor's interactive mode, where the same decisions are enforced against commands that actually run — is introduced later in this guide and walked through end-to-end in the Beta Tester Guide (Path A).

Run the policy engine at a terminal with no arguments:

./aishell-gate-policy
aishell-gate-policy  [educational mode]
Preset: ops_safe       Default-deny: yes  Net-default-deny: yes
Source: human          User: alice  CWD: /home/alice

This tool assesses commands and explains every flag.
It does NOT execute. To execute, pipe through aishell-gate-exec.
────────────────────────────────────────────────

policy> git status

  ALLOW  git status
  Confirmation required: none  (no prompt needed)
  Flag analysis: all flags are known-safe.

  To execute through the gateway:
    echo 'git status' | aishell-gate-exec --source human

policy> rm -rf /var/log

  DENY   rm -rf /var/log
  Reason: rm -rf: recursive force delete denied
  Detail: -rf
  Fix:    Remove or replace the denied argument.

policy> ls -la

  ALLOW  ls -la
  Confirmation required: none  (no prompt needed)
  Flag analysis: all flags are known-safe.

  To execute through the gateway:
    echo 'ls -la' | aishell-gate-exec --source human

policy> quit

The default preset is ops_safe, which allows a conservative set of read-only and repository inspection commands. You will see this behavior without any configuration files in place.

Type json after any command to see the full JSON decision record for the previous evaluation. Type quit or exit to leave.

A note on how this is supposed to feel The interactive policy engine takes no flags, needs no config files, and creates no files. Run the binary, type any command, learn by watching. This is deliberate. Software that asks you to understand it before it lets you use it wastes the user's time — we have spent enough hours of our own lives on that kind of software to want the opposite here.

Why a Human Would Use This

The policy engine's flag catalog carries a documented reason for every flag that raises risk — not a generic label, a specific plain-English explanation:

An unknown flag is explicitly labelled "risk unassessed, not evaluated as safe" — the system never implies it checked something and found it safe when it did not. For a junior operator, this is instruction delivered in context at the moment of the command. For an experienced operator, it is a disciplined workflow with confirmation gates and full audit trail on every command run.

10 Choosing a Preset

A preset selects a named command allow/deny configuration for the builtin policy layer. You select one with --policy-preset. Presets replace the builtin layer's cmd_allow and cmd_deny lists while leaving arg_rules, path_rules, and net_rules unchanged. Seven presets are built in; three ship in Standard edition and four require Enterprise:

If you are just starting: use ops_safe for read-only exploration or dev_sandbox for hands-on code work. The CI presets require specific runtime conditions (--jail-root, batch mode), Enterprise edition, and are not meant for interactive evaluation.
PresetDescription
read_onlyAllows inspection commands only: ls, cat, grep, find, ps, df, git status, git log, and similar. Denies everything that writes, deletes, or escalates privilege. Use this when you want to let an AI agent observe a system without being able to change anything.
dev_sandboxAllows developer workflow commands: git, make, compilers, package managers (pip, cargo, go, npm). Blocks disk destructors and privilege escalation. A reasonable starting point for AI-assisted coding workflows.
ops_safeThe default. A small conservative set of read and repository commands. Denies shells, interpreters, most write operations, and all privilege commands.
ci_build [ENTERPRISE]Unattended build and test pipeline. All allowed commands carry CONFIRM_NONE — confirmation prompts would hang a headless CI runner. Safety comes from the allow list boundary and --jail-root. Requires --jail-root to constrain file operations.
ci_deploy [ENTERPRISE]Unattended deployment pipeline. Superset of ci_build adding remote transfer, container operations, Kubernetes, Helm, Terraform, and service restart. Destructive operations (kubectl delete, helm uninstall, terraform destroy) remain in deny.
ci_admin [ENTERPRISE]CI/CD infrastructure management with mixed confirmation levels. Reads and inspections at CONFIRM_NONE; writes and destructive operations at CONFIRM_ACTION; terraform destroy and interactive shells at CONFIRM_TYPED. Intended for supervised admin sessions.
danger_zone [ENTERPRISE]Minimal restrictions. A wildcard allow rule permits most commands, but the built-in deny list (shells, interpreters, disk destructors, privilege escalation) still applies. Use cautiously and only when you have a good reason. All commands require typed confirmation.

Whatever preset you choose, confirmation levels and risk scoring always apply on top of it. A high-risk command does not bypass confirmation just because the preset allows it.

11 Risk and Confirmation

When the policy engine allows a command, it also assigns a confirmation level that tells the executor how much human review is required before the command actually runs. There are four levels:

LevelMeaning
noneProceed immediately — no human review needed.
planShow the plan to a human before executing; review is suggested.
actionRequire explicit per-command human approval.
typedThe human must type a confirmation code derived from the exact command text.

Your policy rules can set a confirmation level for any allowed command. But the engine also scores every command for risk on a scale of 0 to 100, and will automatically raise the confirmation level if the score is high enough:

Risk-based escalation is strictly one-way. The risk score can only raise a confirmation level, never lower it. Within a single policy layer, the flag catalog similarly only raises. Cross-layer behaviour is different: policy layers are scanned highest-authority first, and the first layer that produces any match wins outright — lower-authority layers are not consulted for their allow rules, their deny rules, or their confirmation levels. A user-layer allow rule with confirm: none therefore takes effect even if a base-layer rule would have required typed, because the base layer is not read. See §14 for the layering model and how to position rules that must not be overridden.

Some representative scores: ls and cat score 0–5 and require no confirmation. git scores 20 and requires a plan-level review. curl and wget score 60 and require action confirmation. rm scores 80 and requires typed confirmation. dd, parted, mkfs, and wipefs score 95–98, also requiring typed confirmation. Arguments make the score worse: targeting a system path adds 15 points, a recursive flag on a destructive command adds 10, --force with rm or mv adds 10.

12 Running a Plan Through the Executor

For real use, you do not call the policy engine directly. You pass a JSON plan to aishell-gate-exec, and it handles the rest. The plan format is simple: a goal description, an optional strategy, and an array of commands. (Caller identity is set with the --source flag, not in the plan — any source field in the JSON is ignored.)

echo '{
  "protocol": {"name": "aishell-gate-exec-input", "version": "1.0"},
  "goal":     "check repository state",
  "strategy": "fail_fast",
  "actions": [
    {"cmd": "git pull"},
    {"cmd": "git status"},
    {"cmd": "npm test"}
  ]
}' | ./aishell-gate --policy-preset dev_sandbox

The executor submits each command to the policy engine in sequence, reads the JSON verdict back, collects human confirmation if the confirmation level requires it, and calls execve() with the validated argument array from the verdict. The raw command string never touches a shell.

A policy denial on any action refuses the entire plan before anything runs — independent of strategy. The strategy field governs only runtime behaviour once execution has begun: fail_fast stops at the first command that exits non-zero; best_effort attempts the remaining allowed, confirmed actions and reports the first failure.

Keep it simple: When aishell-gate, aishell-gate-exec, and aishell-gate-policy all live in the same directory and you run from that directory, no --policy-binary flag is ever needed. The executor locates the policy engine at ./aishell-gate-policy by default, and aishell-gate handles co-location automatically. You only pass --policy-binary explicitly when binaries are installed elsewhere and you are calling aishell-gate-exec directly rather than via aishell-gate.

aishell-gate can also be called per-command from inside an existing shell script to add policy evaluation and an audit trail without rewriting the script's logic — a reasonable migration path for legacy scripts, though a JSON plan is the interface the system is actually designed around.

13 Evaluating a Single Command

You can pipe a single command to the policy engine directly, without using the executor or writing a JSON plan. This is useful for scripting, testing, or integrating the policy check into other tools.

echo "git status" | ./aishell-gate-policy --policy-preset dev_sandbox

Add --json to get the full machine-readable output:

echo "git status" | ./aishell-gate-policy --policy-preset dev_sandbox --json

The JSON output includes the overall decision, the confirmation level, the matched rule, the policy layer that matched it, the validated argument array, the risk score and flags, the blast radius classification, and a short plain-text summary suitable for display to a human.

If you are integrating this into a script, the exit code from the policy engine is always 0 when it runs without internal error — regardless of whether the decision was ALLOW or DENY. The decision itself lives in the JSON output. Exit code 1 means an internal error; exit code 2 means a usage problem. This allows the policy engine to be used in pipelines without being mistaken for a command failure. The calling script reads the JSON to determine what happened.

A scripting hook worth noticing This mode — single command in, JSON decision out, exit code reserved for internal errors — is the integration point for CI pipelines, test harnesses, pre-commit hooks, and policy regression suites. Any wrapper that needs a policy decision without actually running anything can shell out to aishell-gate-policy with --json and parse the result. If the thought had not occurred yet: this is how you bolt the gate's reasoning into systems that do their own execution.

Policy Decision Fields

Every policy decision — whether from the interactive engine, the executor, the MCP server, or the pipe scripts — contains the same fields. This is the authoritative field reference:

FieldMeaning
decisionallow or deny
confirmRequired confirmation level: none, plan, action, or typed
layerPolicy layer that produced the decision
reasonHuman-readable explanation of why the decision was made
risk.scoreInteger 0–100
risk.blast_radiussingle, tree, system, or unknown
argvValidated argument vector — passed to execve() on ALLOW decisions
suggestionsAllowed alternatives — may be present on DENY decisions when alternatives exist

If the decision is deny, the action is refused and the reason and suggestions fields explain what happened and what alternatives are available. If the decision is allow, the executor proceeds according to the confirm level.

JSON shape. These fields live on each action inside an envelope. The top level is { "session": {…}, "request": {…}, "plan": { "actions": [ {…} ], "overall_decision": "allow|deny" } }, so a single command's decision is at plan.actions[0] and risk is nested (risk.score, risk.blast_radius). Parse the envelope, not a flat top-level object.
Everything already in the toolbox Because execution happens as a normal process, the full range of standard Unix observability tools applies without adaptation. ps, top, lsof, and strace see honest process trees. Audit logging goes to a file, and SSH logs to syslog — both feed existing log aggregation pipelines without requiring adapters or agents. There is nothing hidden behind a proprietary protocol, no opaque daemon, no special tooling needed to answer the question "what is the AI actually doing right now."

Confirmation Levels

Some commands require a human operator to confirm before execution. This is a policy decision — neither the AI nor the executor controls it. Confirmation for all actions is collected in a single pass before any command runs, so a mid-plan refusal cannot leave the system in a partially-executed state.

LevelBehaviour
noneProceed without any confirmation prompt.
planPrompt once before execution begins. Operator types y to proceed.
actionPrompt for each individual command. Operator types yes.
typedOperator must type a short challenge code derived from the exact command text. Used for high-risk operations where muscle memory alone should not suffice.

By default, aishell-gate-exec opens /dev/tty for confirmation prompts — the controlling terminal of the process, separate from stdin. This works correctly in any interactive session and is available in both editions via --confirm-tty. In non-interactive deployments such as SSH forced-command sessions, relay confirmation mode is used — available in both editions: you must be logged into the same server as aishell-gate in a separate terminal running aishell-gate-confirm — that session provides the human's TTY while the LLM running on a remote server is logged into the local server as user ai-agent. The Remote Deployment Guide §12 walks through the full setup: the FIFO pair that connects the headless ai-agent session to your interactive operator terminal, and the aishell-gate-confirm command that reads the request, displays it to you, and sends your response back. For the MCP server relay see the MCP guide §05.

The confirmation system is working when it fires A confirmation requirement surfacing is the policy engine doing exactly what it was designed to do — flagging a command that a human should review before it runs. The right response is not to suppress it. Either choose a preset that produces confirm: none for the commands the AI will run, or wire up the appropriate relay so a human can respond. Test the exact plan shapes the AI will submit against the exact preset you have configured before going to production. Any action showing a confirmation level other than none belongs either in a workflow where a human is present, or in a policy rule that explicitly reduces its requirement.

14 Writing Your First Policy File

Presets are a starting point. For real use you will want to layer your own rules on top. Three optional JSON files are evaluated on top of the selected preset:

By default the engine looks for all three files in the current directory. You can point to different paths with --policy-base, --policy-project, and --policy-user. Standard edition loads only the user-layer file; --policy-base and --policy-project require Enterprise edition and are rejected with a clear "not available in standard edition" message on a Standard binary. If a file is absent it is silently ignored. If it exists but cannot be parsed, the engine fails closed and reports the problem.

Layer precedence. Authority runs user > project > base > preset. Layers are scanned highest-authority first, and the first layer that produces any matching rule — allow or deny — wins outright. Lower-authority layers are not consulted once a higher-authority layer has matched. Within the winning layer, deny rules are evaluated before allow rules. This means a higher-authority layer can override a lower-authority layer in both directions: it can allow what a lower layer would have denied, and it can deny what a lower layer would have allowed. The "base" layer is a starting baseline, not an unoverridable floor. If you need a rule that cannot be relaxed by a higher layer, the layer that carries it must sit at or above every layer operators are permitted to modify — which for single-operator deployments typically means placing non-negotiable rules in the user layer, or locking down file-system access to the higher-authority layer files in multi-tenant deployments.

Here is a minimal project policy that allows a few specific git commands without confirmation, requires action confirmation for npm publish, and blocks curl entirely:

{
  "cmd_allow": [
    { "pattern": "git status",  "confirm": "none",   "reason": "safe read" },
    { "pattern": "git diff",    "confirm": "none",   "reason": "safe read" },
    { "pattern": "npm test",    "confirm": "plan",   "reason": "run tests" },
    { "pattern": "npm publish", "confirm": "action", "reason": "publish step" }
  ],
  "cmd_deny": [
    { "pattern": "curl", "reason": "no outbound network in this env" }
  ],
  "writable_dirs": [ "/home/user/myproject" ]
}

Save this as aishell-gate-policy_project.json in your working directory. It will be picked up automatically on the next run.

A few things worth knowing. Rule lists append to the preset by default. If you want your project policy to replace the preset's cmd_allow list entirely rather than extend it, add "cmd_allow_replace": true alongside your cmd_allow array. Unknown keys in a policy file are an immediate hard error, not a silent no-op — a typo like "cmd_denny" will fail the load and tell you what went wrong. A failed load has zero effect on the running policy; the engine restores the previous state atomically.

Run --help-policy at any time to print the complete policy format reference:

./aishell-gate-policy --help-policy

For worked examples beyond the minimal one above — including a realistic web-application policy and sample plans matched to common scenarios — see the Policy Reference and sample files in doc/policy/. Most beta testers won't need these right away; they're there when you do.

15 Argument and Path Rules

Beyond allowing or denying commands by name, you can write rules that match on specific arguments or on the paths a command targets. These let you allow a command in general while blocking specific dangerous invocations of it.

An argument rule matches a glob pattern against each argument of a given command. This example denies rm with the -rf flag regardless of what the preset would otherwise say:

{
  "arg_rules": [
    { "cmd_pattern": "rm", "arg_glob": "-rf", "decision": "deny",
      "reason": "rm -rf blocked by project policy" }
  ]
}

A path rule matches against the canonicalized path of any argument. Path rules apply only to commands that the engine classifies as write-capable — read-only commands like ls or cat are not subject to path rule evaluation. This example denies any write command that targets the /etc tree:

{
  "path_rules": [
    { "path_glob": "/etc/*", "decision": "deny",
      "reason": "writes to /etc are not permitted" }
  ]
}

Network rules work the same way but match against the hostname extracted from URL arguments. The built-in default base policy already denies 169.254.* and metadata.google.internal to block cloud metadata endpoint access. AIShell-Gate enforces a network default-deny model in ops_safe, read_only, and dev_sandbox presets: any command with a detected network target must have an explicit net_rules allow entry, or it is denied. Add a "net_default_deny": false key to your policy file to opt out for CI pipelines that need unrestricted registry access.

16 Enabling Audit Logging

Every evaluation can be written to a JSON Lines audit log. Add --audit-log with a path to the policy engine to enable it. In Standard edition this log is plain append-only JSON Lines; the tamper-evident hash chain, --audit-verify, and --audit-key described below are Enterprise-only (on a Standard binary they report “not available in the standard edition”):

./aishell-gate-policy --policy-preset ops_safe --audit-log ./policy.jsonl

Each log entry carries a sequence number, session identifier, the full decision context, and a SHA-256 hash that links it to the previous entry. A gap in sequence numbers or a hash mismatch identifies deleted or altered entries. Verify the chain at any time without interrupting operation:

./aishell-gate-policy --audit-verify ./policy.jsonl

For environments that need authenticated audit trails, generate a key and enable HMAC-SHA256 mode:

head -c 64 /dev/urandom > audit.key && chmod 640 audit.key
./aishell-gate-policy --audit-key audit.key \
  --audit-log ./policy.jsonl \
  --policy-preset ops_safe

With a key, only a holder of that key can forge valid chain hashes. The policy key file contains 64 raw binary bytes. The audit log file is append-only; the engine does not rotate it. Protect it with appropriate filesystem permissions.

Two separate audit logs. The policy engine and the executor each write their own independent audit log in a different internal format. The key points are tabulated below. Never point both programs at the same log file, and verify each log only with the binary that wrote it.
 Policy logExec log
Chain field in JSONentry_hashchain_hmac
Chain algorithmSHA-256 sentinel-substitutionHMAC-SHA256
Verifieraishell-gate-policy --audit-verifyaishell-gate-exec --audit-verify
Default log pathnone (off unless --audit-log passed)/var/log/aishell/audit.log
Env var for log pathnoneAISHELL_EXEC_AUDIT_LOG
HMAC key file format64 raw binary bytes64 ASCII hex characters (32 bytes)
Generate keyhead -c 64 /dev/urandomdd if=/dev/urandom bs=32 count=1 | xxd -p | tr -d '\n'

The two HMAC key file formats are not interchangeable. A policy key fed to the executor (or vice versa) will fail key validation at load time.

The executor has its own separate audit log for the execution side. Add --audit-log to the aishell-gate invocation to enable it:

./aishell-gate \
  --policy-preset ops_safe \
  --audit-log ./exec.jsonl

Runtime files and NFS. The confirmation lock and the audit log both use flock(2), which can be unreliable — sometimes silently a no-op — on NFS depending on the server and mount options. Keep the audit log, lock file, and any confirmation FIFOs on a local filesystem (ext4, xfs, tmpfs); never on NFS.

17 Confining Commands to a Directory

The --jail-root flag tells the policy engine to enforce path containment during evaluation. Any write-like command whose path arguments fall outside the jail root will be denied, regardless of what the policy rules say:

./aishell-gate-policy --jail-root /home/user/myproject --policy-preset dev_sandbox

This is the only sandbox enforcement that happens inside the policy engine itself. The other sandbox modes (chroot, container, userns) are advisory hints that are passed through to the executor in the JSON output, where the surrounding infrastructure can act on them.

The jail root check is strict about directory boundaries. A jail root of /tmp/jail will not accidentally allow /tmp/jailbreak/x — the path must be actually inside the named directory, not merely share its prefix.

18 A Complete Example

Here is what a full setup looks like for a developer workflow: a project policy file, an audit log, a jail root, and a plan passed through the executor.

First, create the project policy file:

# aishell-gate-policy_project.json
{
  "cmd_allow": [
    { "pattern": "git status", "confirm": "none" },
    { "pattern": "git diff",   "confirm": "none" },
    { "pattern": "git pull",   "confirm": "plan" },
    { "pattern": "make",       "confirm": "plan" },
    { "pattern": "npm test",   "confirm": "plan" }
  ],
  "cmd_deny": [
    { "pattern": "curl", "reason": "no outbound network" },
    { "pattern": "wget", "reason": "no outbound network" }
  ],
  "writable_dirs": [ "/home/user/myproject" ]
}

Then run a plan through aishell-gate with audit logging:

echo '{
  "goal":     "pull latest and run tests",
  "strategy": "fail_fast",
  "actions": [
    {"cmd": "git pull"},
    {"cmd": "make clean"},
    {"cmd": "npm test"}
  ]
}' | ./aishell-gate \
  --policy-preset dev_sandbox \
  --audit-log ./exec.jsonl

The executor will submit each command to the policy engine. git pull scores in the plan-level range — the executor pauses and shows the plan for human review. After confirmation, it executes via execve(). If any action is denied, the entire plan is refused before anything runs (independent of strategy). An audit entry is written for each decision — a tamper-evident hash chain in Enterprise, plain append-only in Standard.

What the Confirmation Prompt Looks Like

When the executor reaches a command that requires human approval, it pauses and prints a prompt to your terminal. The exact text depends on the confirmation level.

At the plan level — an entire plan that contains one or more plan-level actions is displayed as a numbered list, followed by a single yes-or-no gate:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  PLAN REVIEW  (3 actions)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  [1] ALLOW  git pull
  [2] ALLOW  make clean
  [3] ALLOW  npm test

  Proceed with this plan? [y/N] 

Type y (or yes) and press Enter to proceed. Anything else — empty input, n, Ctrl-C — is treated as a refusal and no commands run.

At the action level — each such action fires its own approval prompt after the plan evaluation summary:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  CONFIRMATION REQUIRED  (action 2)
  Level:   action
  Command: curl https://example.com/install.sh
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ⚠  Explicit operator approval required.
  Review the evaluation summary above carefully.

  Approve? [yes/NO] 

Only the literal string yes is accepted as approval at this level. Anything else is refusal.

At the typed level — the highest tier, used for truly destructive operations — the prompt displays a challenge code derived from the exact command text. The operator must type that code back:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  CONFIRMATION REQUIRED  (action 1)
  Level:   typed
  Command: rm -rf /home/user/myproject/build
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ⚠  HIGH-RISK — Command-specific typed confirmation required.
  Carefully read the command above, then type the code below.
  This code is derived from the exact command text.

  Challenge code:  A7K2-9QWM

  Type code: 

A mismatch between what the operator types and the challenge code is treated as refusal. The code is a friction-on-purpose mechanism: it cannot be muscle-memory approved, and it forces visual confirmation that the command about to run is the command intended.

19 What the Policy Engine Will Not Do

A few things are worth being explicit about, especially if you are coming from a security background.

No quoted arguments. Quotes are shell syntax, and the engine deliberately rejects all shell metacharacters before tokenization. This is intentional: supporting shell quoting would require implementing a shell grammar subset, introducing the same parsing ambiguity and injection surface the system is designed to eliminate. A command like grep 'hello world' file.txt cannot be expressed; the single quotes are rejected outright. Arguments with spaces are not supported.

Network rules match hostnames, not resolved IPs. A rule blocking example.com will not catch a URL that resolves to the same IP through a different hostname. Network rules capture intent, not strong enforcement — use firewall rules for that.

The engine is not a sandbox. It decides whether a command should run before it runs. It does not intercept what the command does while running. It complements OS-level access controls, kernel sandboxing, and proper permission management; it does not replace them.

The engine does not guarantee security against a determined attacker with local system access. It raises the cost and visibility of unsafe AI-generated actions and ensures that every attempt is recorded. That is what it is designed to do.

20 Session Policy

Before the engine evaluates any command at all, it checks whether the current session is permitted to make requests. Session policy can gate evaluation on the caller's uid or gid, their username, whether they are in an SSH session, whether stdin is a TTY, the session mode (interactive, batch, or daemon), and the time of day.

Session constraints go in the "session" key of any policy file. Here is an example that restricts operation to two specific users and denies access from SSH sessions:

{
  "session": {
    "allow_users": ["alice", "bob"],
    "deny_ssh": true
  }
}

Session evaluation happens before any command rule is checked. If the session does not satisfy policy, the engine returns a denial without evaluating the command at all. This makes session policy a hard outer gate on the entire system.

21 AI Pipe Scripts

The fastest way to see AIShell-Gate working end-to-end with a real AI model is the pipe script. Describe a goal in plain English, and the script asks the model to produce the JSON plan, strips any formatting the model adds, validates the result, and pipes it through the gate — all in one command. No SSH configuration, no MCP setup, no framework.

One script is provided in the distribution and supports two backends — Anthropic's API and a local Ollama instance — selected with --backend or the AISHELL_BACKEND environment variable. Drop it alongside the gate binaries and make it executable:

chmod +x aishell-gate-pipe.sh

Testing Your Claude API Key First

Before using the anthropic backend, verify your API key and connection with a direct curl call. This confirms the key is valid and the API is reachable from your machine before any gate machinery is involved:

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": "Reply with only the word: ok"}]
  }'

A successful response will contain "type": "message" and a content block with the text ok. Any error response will carry a clear "error" key with a message — most commonly an invalid key (authentication_error) or a missing header (invalid_request_error). Get an API key at platform.anthropic.com/api-keys.

API key vs. subscription. The Anthropic API requires a separate API key from platform.anthropic.com with pay-per-token billing. A claude.ai subscription (including Max) does not provide API access.

aishell-gate-pipe.sh

Defaults to the anthropic backend. Requires curl and python3. If ANTHROPIC_API_KEY is not set in the environment, the script prompts for it interactively with hidden input.

# Dry run — policy evaluated, nothing executed
./aishell-gate-pipe.sh --dry-run "check disk usage and list recent logs"

# Live run under dev_sandbox preset
AISHELL_PRESET=dev_sandbox ./aishell-gate-pipe.sh "run the test suite"

# Override the model
AISHELL_MODEL=claude-opus-4-6 ./aishell-gate-pipe.sh "summarise recent git activity"

Pass --backend ollama (or set AISHELL_BACKEND=ollama) to send the goal to a locally running Ollama instance instead. Requires ollama in PATH. The model defaults to mistral for this backend and is overridden the same way, with AISHELL_MODEL:

# Dry run against Ollama with the default model (mistral)
./aishell-gate-pipe.sh --backend ollama --dry-run "check disk usage and list recent logs"

# Use a different local model
AISHELL_BACKEND=ollama AISHELL_MODEL=llama3 ./aishell-gate-pipe.sh "clean build artifacts"

Both backends accept the same environment variables for policy control — AISHELL_PRESET, AISHELL_SAFE_PATH, AISHELL_POLICY_BASE, AISHELL_POLICY_PROJECT, and AISHELL_POLICY_USER — and write to the same audit log. AISHELL_POLICY_BASE and AISHELL_POLICY_PROJECT require Enterprise edition, same as the flags they pass through to. Always start with --dry-run to confirm the generated plan looks correct before allowing live execution.

Complete environment variable reference

VariableDefaultPurpose
AISHELL_BACKENDanthropicAI backend: anthropic or ollama.
AISHELL_MODELbackend-dependentModel override. claude-sonnet-4-6 for anthropic, mistral for ollama.
ANTHROPIC_API_KEYnoneRequired for the anthropic backend. Prompted interactively (hidden input) if unset.
AISHELL_PRESETops_safePolicy preset passed to aishell-gate-exec via --policy-preset.
AISHELL_GATE_EXEC./aishell-gate-execPath to the executor binary. (Pipe-script variable; the aishell-gate wrapper in §07 uses AISHELL_EXEC_BIN/AISHELL_POLICY_BIN instead.)
AISHELL_GATE_POLICY./aishell-gate-policyPath to the policy engine binary, passed via --policy-binary.
AISHELL_AUDIT_LOGaishell-audit.jsonlAudit log file path.
AISHELL_SAFE_PATHunsetColon-separated directory list for command-name resolution, passed via --safe-path.
AISHELL_POLICY_BASEunsetBase-layer policy override file, passed via --policy-base. [ENTERPRISE]
AISHELL_POLICY_PROJECTunsetProject-layer policy override file, passed via --policy-project. [ENTERPRISE]
AISHELL_POLICY_USERunsetUser-layer policy override file, passed via --policy-user.

The base, project, and user policy override files compose with the active preset under the standard four-layer policy stack — see §14 above.

Exit codes

The script passes through the exit code from aishell-gate-exec unchanged:

CodeMeaning
0All actions in the plan completed successfully.
1One or more actions denied by policy.
2Operator refused a confirmation, or no operator available.
3Policy engine subprocess error.
4JSON parse error in the plan or in a policy response.
5Argument or startup error in aishell-gate-exec.
6The gateway could not execute the command after an ALLOW decision — binary not found in the safe path, or a fork/exec failure. The command never ran.
7All gates passed and the command ran, but it returned a non-zero exit status of its own. The command's real exit code appears on stderr as [gate-exec] action N exited M.
8The command ran and the gateway killed it for exceeding --action-timeout. Separate from 7 because a failed command may be worth re-running, while a command the gate had to kill should not be retried unchanged.
128+NExecution interrupted, or a command killed from outside, by signal N.
Why 7 is separate from 1. Codes 1–6 describe what the gateway decided or could not do; they never carry a command's own exit status. rm nofile exits 1 on its own, and before 0.57.0 that was indistinguishable from a policy denial. A caller reading exit codes — an AI agent above all — would be told policy blocked a command policy had allowed, and would rewrite the command to satisfy a rule that never objected. If you have scripts testing for a specific command exit status through the gate, read the action N exited M stderr line or the audit log instead.

The wrapper itself returns exit code 1 for its own startup errors — missing binaries, missing ollama when the ollama backend is selected, an empty model response, or an unreadable policy override file — distinct from the passthrough codes above, which come from the executor.

Why this works without a framework. The executor does not know or care whether the JSON plan came from a hand-written file, a shell variable, an AI model, a CI system, or a test harness. It reads stdin. Anything that can write a valid JSON plan to stdout can drive AIShell-Gate. The gate is the boundary; what is on the other side of it is your choice.

22 Privileged Operations — Working Without Root

Sooner or later an AI-driven task will appear to need root: restart a service, install a package, read a protected log. This section is the system's position on that situation. The short version: AIShell-Gate never runs as root, most tasks that look like they need root don't, and for the few that genuinely do there is one supported pattern.

The design stance

Root is refused structurally, at three independent layers. The aishell-gate launcher refuses to start as root before any executor code runs. It also refuses setuid or setgid binaries in the exec or policy position — a setuid binary there would be a local privilege escalation surface. And sudo sits on the built-in deny list of every preset, including danger_zone.

The reasoning is the same one that runs through the whole system: the gate's value is that a mistake costs only what the invoking account can touch. An AI-generated command running as root has no such ceiling — one wrong path in one confirmed command and the blast radius is the machine. The policy engine reduces the probability of that command; refusing root caps its cost. Both layers are needed, and the second one is not negotiable in this design.

First resort: lower the requirement instead of raising the privilege

Most "this needs root" tasks are really "this needs one narrow permission that root happens to include." Granting that one permission to the agent's account — once, by a human, outside the gate — turns the task into an ordinary unprivileged command that your existing preset already evaluates. No policy changes, no sudo, nothing new to audit.

Reading protected logs — group membership, not root:

# One-time, by the administrator
sudo usermod -aG adm ai-agent              # /var/log on Debian/Ubuntu
sudo usermod -aG systemd-journal ai-agent  # journalctl access

Managing specific services — a polkit rule scoped to exactly the units the agent may touch, nothing else:

# /etc/polkit-1/rules.d/50-ai-agent-nginx.rules
polkit.addRule(function(action, subject) {
  if (action.id == "org.freedesktop.systemd1.manage-units" &&
      action.lookup("unit") == "nginx.service" &&
      subject.user == "ai-agent") {
    return polkit.Result.YES;
  }
});

After which systemctl restart nginx is an unprivileged command for ai-agent — evaluated, confirmed, and audited by the gate like any other.

Writing to protected directories — ownership or ACLs on the specific tree, ideally combined with --jail-root:

sudo setfacl -R -m u:ai-agent:rwX /srv/deploy
sudo setfacl -dR -m u:ai-agent:rwX /srv/deploy

Binding low ports or raw sockets — a file capability on the one binary that needs it:

sudo setcap cap_net_bind_service=+ep /usr/local/bin/the-server

Each of these is a deliberate, visible, one-time grant made by a human administrator. That is the correct division of labour: humans expand the boundary; the gate enforces activity inside it.

Last resort: exact-command sudo, gated twice

When a task genuinely requires root and cannot be decomposed — a package install, say — the supported pattern uses two independent layers with distinct jobs: sudoers pins the exact command; the policy layer gates and audits it.

Layer 1 — sudoers pins the command. A drop-in grants the agent's account NOPASSWD for the complete, literal command lines it may run — and nothing else. This is the layer that enforces exactness:

# /etc/sudoers.d/ai-agent — validate with visudo -c before saving
ai-agent ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
ai-agent ALL=(root) NOPASSWD: /usr/bin/apt-get update

Layer 2 — policy gates and audits it. Because every layer above builtin can override the builtin deny on a per-rule basis (user > project > base > builtin), a user or project override file re-admits exactly the sudo invocations that sudoers permits, at confirm level action or higher:

{
  "cmd_allow": [
    { "pattern": "sudo systemctl", "confirm": "action",
      "reason": "service restart per sudoers whitelist" },
    { "pattern": "sudo apt-get",   "confirm": "action",
      "reason": "package refresh per sudoers whitelist" }
  ]
}

The division of labour matters. Policy patterns match the command and subcommand — sudo systemctl — not the full argument list, so the policy layer alone would admit sudo systemctl stop firewalld just as readily as the restart you intended. It is the sudoers line that narrows "sudo systemctl anything" down to the one literal command that will actually execute; sudo refuses everything else with a clear denial in its own log. The policy layer contributes what sudoers cannot: the confirmation gate, the risk assessment, and the entry in the audit chain. Either layer alone is incomplete; together each covers the other's blind spot. And because the executor passes the pre-tokenized argv directly to execve(), sudo is simply the resolved binary and the real command is its literal arguments — there is no shell in between to reinterpret anything.

The sudoers line is the exactness guarantee — never loosen it. Command-line wildcards in sudoers (systemctl restart *, or a bare command path with no arguments, which sudo treats as "any arguments") silently convert this pattern from "one authorized command" into "a family of commands the policy engine cannot distinguish." Every sudoers entry in this pattern must spell out the full command line, argument by argument. If the exact line can't be written down in advance, the task is not a candidate for this pattern — decompose it or do it by hand.
MCP and root work. Confirm level typed is structurally blocked over the MCP interface — typed confirmation requires a human at a terminal. Setting sudo rules to typed therefore makes privileged commands impossible from Claude Code or Cursor and possible only in an interactive terminal session. That is a feature, not a limitation: it lets you decide, per rule, whether root work is available to autonomous channels at all.

Not supported

Running any AIShell-Gate component as root. Setuid or setgid on the binaries. Blanket or wildcard sudo rules. Adding the agent's account to the sudo or wheel group. Each of these removes the cost ceiling that the rest of the system is built to preserve, and the launcher's pre-flight checks refuse the first two outright.

23 Where to Go Next

You now know how to use AIShell-Gate interactively at a terminal and how to drive it from an AI model via the pipe scripts. The same gate, the same policy file, and the same audit chain extend to two further deployment shapes:

The arc continues Interactive mode is the foundation. The pipe scripts are the fast path to a live AI-driven workflow on the same machine. Remote deployment applies the same gate to a distant actor over SSH. MCP delivers it directly into an AI coding environment. aishell-gate-chat.py puts a conversational interface in front of the same gate for operators who want AI assistance without autonomous execution. Same gate, same decisions, same audit — five surfaces onto one policy boundary.

24 Quick Reference

Policy engine interactive (no execution)

./aishell-gate-policy

Run a plan through aishell-gate

./aishell-gate --policy-preset ops_safe --audit-log ./exec.jsonl
echo '<json-plan>' | ./aishell-gate --policy-preset dev_sandbox

Evaluate a single command (no executor, no plan)

echo "git status" | ./aishell-gate-policy --policy-preset dev_sandbox --json

Run a plan from a file

cat plan.json | ./aishell-gate \
  --policy-preset dev_sandbox \
  --audit-log ./exec.jsonl

Inspect a plan without executing (dry-run JSON)

cat plan.json | ./aishell-gate \
  --policy-preset ops_safe \
  --dry-run-json

Run a policy test suite [ENTERPRISE]

./aishell-gate-policy --policy-preset ops_safe --test-plan tests/policy_tests.json

Verify an audit log [ENTERPRISE]

./aishell-gate-policy --audit-verify ./policy.jsonl

Read the policy format reference

./aishell-gate-policy --help-policy

For complete option documentation, policy file syntax, session policy fields, exit codes, and known limitations, see the man page: aishell-gate-policy(1) (the full list of man pages and what each covers is in README). For connecting an AI model as a source of JSON plans, see §21 AI Pipe Scripts in this guide. For the security architecture and design rationale, see the AIShell-Gate white paper.