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
--dry-run first: it performs full policy
evaluation and fires all confirmation gates but executes nothing../aishell-gate --policy-preset ops_safe --dry-runAIShell-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.
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).
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 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.)
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.
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.
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:
sed -i — "in-place edit; writes back to source file"touch -t — "timestamp forgery can backdate files to evade time-based audit trails"dmesg -c — "clears the kernel ring buffer after reading — destroying evidence impedes forensic investigation"make -t — "touch mode updates timestamps without executing commands — creates misleading audit trail about what was built when"tcpdump -w — "writing raw packets to disk captures full payload including cleartext credentials"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/
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.
| Interface | Who it's for | Where it's covered |
|---|---|---|
| Interactive policy engine | Anyone learning the system or auditing a policy by hand. No AI, no execution, no config. | §02 above |
| Interactive executor | Operators who want the same evaluation and audit trail on commands that actually run. | §04 below |
| JSON plan pipe | Anything that can write a JSON plan to stdout — CI, test harnesses, hand-written files, custom agents. | §04 below |
| Single-command check | CI pipelines, pre-commit hooks, regression suites — a policy decision with nothing executed. | §04 below |
| curl / pipe / chat | Seeing 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 agent | An autonomous AI agent on a different machine, with a human confirming from a separate terminal. | Remote Deployment Guide |
The policy engine alone never executes. To run something, pick a preset and pass a command (or a plan) through aishell-gate.
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.
| Preset | Description |
|---|---|
| read_only | Inspection commands only: ls, cat, grep, find, ps, df, git status, git log. Denies anything that writes, deletes, or escalates privilege. |
| dev_sandbox | Developer workflow commands: git, make, compilers, package managers (pip, cargo, go, npm). Blocks disk destructors and privilege escalation. |
| ops_safe | The 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.
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.
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.
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.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.)
| Field | Meaning |
|---|---|
| decision | allow or deny |
| confirm | Required confirmation level: none, plan, action, or typed |
| layer | Policy layer that produced the decision |
| reason | Human-readable explanation of why |
| risk.score | Integer 0–100 |
| risk.blast_radius | single, tree, system, or unknown |
| argv | Validated argument vector — passed to execve() on ALLOW |
| suggestions | Allowed 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.
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.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.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.
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.
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.
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.
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.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:
Human operator path:
Both paths use the same policy file. There is no separate human-mode configuration.
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:
aishell-gate-exec and aishell-gate-policy are present and executable.--policy-binary pointing at the verified policy engine — callers and SSH forced commands never need to supply it.exec() to replace itself with aishell-gate-exec. aishell-gate does not remain in the process tree after handoff.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:
| Variable | Default | Purpose |
|---|---|---|
| AISHELL_EXEC_BIN | aishell-gate-exec beside the script | Path to aishell-gate-exec |
| AISHELL_POLICY_BIN | aishell-gate-policy beside the script | Path 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
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.
| Feature | Standard | Enterprise |
|---|---|---|
| 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 | — | ✓ |
not available in standard edition message and exits cleanly — it does not silently ignore the flag or produce a generic unknown-option error.--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.
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.
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:
sed -i — "in-place edit; writes back to source file"touch -t — "timestamp forgery can backdate files to evade time-based audit trails"dmesg -c — "clears the kernel ring buffer after reading — destroying evidence impedes forensic investigation"make -t — "touch mode updates timestamps without executing commands — creates misleading audit trail about what was built when"tcpdump -w — "writing raw packets to disk captures full payload including cleartext credentials"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.
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:
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.| Preset | Description |
|---|---|
| read_only | Allows 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_sandbox | Allows 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_safe | The 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.
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:
| Level | Meaning |
|---|---|
| none | Proceed immediately — no human review needed. |
| plan | Show the plan to a human before executing; review is suggested. |
| action | Require explicit per-command human approval. |
| typed | The 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:
planactiontypedRisk-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.
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.
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.
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.
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.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:
| Field | Meaning |
|---|---|
| decision | allow or deny |
| confirm | Required confirmation level: none, plan, action, or typed |
| layer | Policy layer that produced the decision |
| reason | Human-readable explanation of why the decision was made |
| risk.score | Integer 0–100 |
| risk.blast_radius | single, tree, system, or unknown |
| argv | Validated argument vector — passed to execve() on ALLOW decisions |
| suggestions | Allowed 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.
{ "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.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."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.
| Level | Behaviour |
|---|---|
| none | Proceed without any confirmation prompt. |
| plan | Prompt once before execution begins. Operator types y to proceed. |
| action | Prompt for each individual command. Operator types yes. |
| typed | Operator 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.
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.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:
aishell-gate-policy_base.json — organization-level defaults [ENTERPRISE]aishell-gate-policy_project.json — project-specific rules [ENTERPRISE]aishell-gate-policy_user.json — per-user preferences on topBy 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.
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.
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.
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.
| Policy log | Exec log | |
|---|---|---|
| Chain field in JSON | entry_hash | chain_hmac |
| Chain algorithm | SHA-256 sentinel-substitution | HMAC-SHA256 |
| Verifier | aishell-gate-policy --audit-verify | aishell-gate-exec --audit-verify |
| Default log path | none (off unless --audit-log passed) | /var/log/aishell/audit.log |
| Env var for log path | none | AISHELL_EXEC_AUDIT_LOG |
| HMAC key file format | 64 raw binary bytes | 64 ASCII hex characters (32 bytes) |
| Generate key | head -c 64 /dev/urandom | dd 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.
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.
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.
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.
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.
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.
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
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.
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.
| Variable | Default | Purpose |
|---|---|---|
AISHELL_BACKEND | anthropic | AI backend: anthropic or ollama. |
AISHELL_MODEL | backend-dependent | Model override. claude-sonnet-4-6 for anthropic, mistral for ollama. |
ANTHROPIC_API_KEY | none | Required for the anthropic backend. Prompted interactively (hidden input) if unset. |
AISHELL_PRESET | ops_safe | Policy preset passed to aishell-gate-exec via --policy-preset. |
AISHELL_GATE_EXEC | ./aishell-gate-exec | Path 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-policy | Path to the policy engine binary, passed via --policy-binary. |
AISHELL_AUDIT_LOG | aishell-audit.jsonl | Audit log file path. |
AISHELL_SAFE_PATH | unset | Colon-separated directory list for command-name resolution, passed via --safe-path. |
AISHELL_POLICY_BASE | unset | Base-layer policy override file, passed via --policy-base. [ENTERPRISE] |
AISHELL_POLICY_PROJECT | unset | Project-layer policy override file, passed via --policy-project. [ENTERPRISE] |
AISHELL_POLICY_USER | unset | User-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.
The script passes through the exit code from aishell-gate-exec unchanged:
| Code | Meaning |
|---|---|
| 0 | All actions in the plan completed successfully. |
| 1 | One or more actions denied by policy. |
| 2 | Operator refused a confirmation, or no operator available. |
| 3 | Policy engine subprocess error. |
| 4 | JSON parse error in the plan or in a policy response. |
| 5 | Argument or startup error in aishell-gate-exec. |
| 6 | The 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. |
| 7 | All 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. |
| 8 | The 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+N | Execution interrupted, or a command killed from outside, by signal N. |
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.
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.
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.
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.
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.
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.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.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.
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:
authorized_keys entry forces aishell-gate as the command, and every plan the agent submits is evaluated by the same policy engine you just learned. Human confirmation at higher levels is relayed to an operator in a second terminal via aishell-gate-confirm, available in both editions. The Remote Deployment Guide covers the full setup: the dedicated ai-agent account, constrained-account hardening, the forced SSH command configuration, the operator relay, and the system-wide install steps (shared group, runtime directories, multi-session confirmation pipes) that a single-machine setup like the one in this guide never needs.evaluate_plan and execute_plan as tools; the AI can inspect plans before committing and execute gated plans under the same policy. No pipeline scripting, no SSH configuration.python3 ./aishell-gate-chat.py from the package directory for an immediate interactive demo../aishell-gate-policy
./aishell-gate --policy-preset ops_safe --audit-log ./exec.jsonl
echo '<json-plan>' | ./aishell-gate --policy-preset dev_sandbox
echo "git status" | ./aishell-gate-policy --policy-preset dev_sandbox --json
cat plan.json | ./aishell-gate \
--policy-preset dev_sandbox \
--audit-log ./exec.jsonl
cat plan.json | ./aishell-gate \
--policy-preset ops_safe \
--dry-run-json
./aishell-gate-policy --policy-preset ops_safe --test-plan tests/policy_tests.json
./aishell-gate-policy --audit-verify ./policy.jsonl
./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.