AISHELL-GATE-CONFIRM(1) AIShell-Gate Manual AISHELL-GATE-CONFIRM(1) "AIShell-Gate" Copyright (c) 2026 AIShell Labs LLC Winston-Salem NC USA. All Rights Reserved. Author: Sean T. Gilley Do not remove this notice. Use of this Software requires a valid license. THIS SOFTWARE IS PROVIDED BY AISHELL LABS LLC "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AISHELL LABS LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. For full license terms see the LICENSE file or: www.aishellgate.com NAME aishell-gate-confirm - operator confirmation relay for remote AIShell-Gate deployments SYNOPSIS aishell-gate-confirm [--pipe basepath] [--timeout secs] aishell-gate-confirm --help EDITIONS aishell-gate-confirm is available in both the Standard and Enterprise editions. It implements the operator side of relay confirmation mode -- the --confirm-pipe and --confirm-lock path in aishell-gate-exec(1) -- which is available in both editions. Standard edition also supports single-session interactive confirmation via --confirm-tty, which does not require aishell-gate-confirm. See aishell-gate-exec(1) EDITIONS for the full Standard/Enterprise split. DESCRIPTION aishell-gate-confirm is the operator-side relay for the AIShell-Gate confir- mation system. It creates a pair of named FIFOs, waits for confirmation requests from aishell-gate-exec(1), displays the full command context on the operator's terminal, reads the operator's response, and relays it back. It is required only in remote multi-session deployments; it is not used when an operator runs aishell-gate-exec interactively at a single terminal. Available in both editions (see EDITIONS above). The confirmation system When aishell-gate-policy(1) allows a command, it also assigns a confir- mation level that controls how much human review is required before the command runs. There are four levels: none Proceed immediately -- no human review needed. plan Show the full plan; operator types y to proceed. action Require explicit per-command approval; operator types yes. typed Operator must type an 8-character challenge code derived from the exact command text. Confirmation levels are set by policy rules and raised automatically by risk score. They can only be raised, never lowered. A command scoring 40 or above is raised to at least plan; 70 or above to action; 90 or above to typed. All confirmations for a plan are collected in a single pass before any command runs. A mid-plan refusal cannot leave the system in a par- tially-executed state. The remote confirmation problem In a remote deployment, the AI agent connects to the target machine via SSH under a restricted account with no interactive shell. When a con- firmation fires, aishell-gate-exec needs to present a prompt and read a response from a human operator. There is no controlling terminal on the AI's SSH connection. The question is: where does the prompt appear, and how does the operator respond? Earlier designs attempted to open the operator's PTY device directly from the ai-agent account. This approach has three fatal flaws: o PTY slave devices on Linux are mode 0620 (owner rw, group write- only). The tty group does not grant read access, so fopen(3) with "r+" fails with EACCES in any correctly configured deployment. o Granting ai-agent membership in the tty group gives it write access to every PTY on the machine, enabling keystroke injection into any user's terminal session. o The operator would see only a bare prompt with no context -- the full command description, risk score, and challenge code were written to the AI's stderr channel, not to the operator's terminal. The FIFO relay design aishell-gate-confirm solves the remote confirmation problem by inverting who owns the channel. The operator runs aishell-gate-confirm in their own SSH session. That tool creates two named FIFOs owned by the operator with a shared aishell-gate group: BASEPATH.req aishell-gate-exec writes JSON confirmation requests here; aishell-gate-confirm reads them. BASEPATH.resp aishell-gate-confirm writes plain-text responses here; aishell-gate-exec reads them. The FIFOs are owned by the operator and accessible to the ai-agent ac- count via the shared aishell-gate group. Neither account needs PTY mem- bership. aishell-gate-exec never opens any PTY device. Every piece of information the operator needs -- command text, goal, source, risk score, blast radius, policy reason, and the typed-level challenge code -- travels through the request FIFO to the operator's terminal. Nothing useful reaches the AI's stderr channel. aishell-gate-confirm opens both FIFOs with O_RDWR rather than O_RDONLY or O_WRONLY. A FIFO opened O_RDONLY blocks until a writer appears; O_RDWR returns immediately, keeping aishell-gate-confirm responsive from the moment it starts. This is documented POSIX behaviour for FIFOs on Linux. What the operator sees When a confirmation fires, aishell-gate-confirm displays the full context on the operator's terminal: -------------------------------------------------------------------- AISHELL-GATE CONFIRMATION REQUEST Session: a3f8c21d9e4b7012 Action: 0 Level: action -------------------------------------------------------------------- Command: git push origin main Goal: deploy release v2.4.1 Source: ai Reason: modifies remote branch; requires explicit approval Risk: 72/100 blast=system -------------------------------------------------------------------- Approve? [yes/NO] For typed-level confirmations, the challenge code is shown here and only here. It never appears in the AI's SSH channel: -- HIGH-RISK -- typed confirmation required. Type the challenge code exactly to confirm: Challenge: 3k7mw2nx Type code: For plan-level confirmations, all planned actions are displayed before the single y/N prompt, giving the operator a complete view of scope be- fore approving. The typed challenge is an 8-character alphanumeric code derived from the command text using FNV-1a hashing. Visually ambiguous characters (0, 1, i, l, o) are excluded so the operator can read and type the code without misreading a character. The code changes with every unique com- mand, proving the operator read the specific command being authorised rather than muscle-memorying a fixed response. 31^8 ~ 852 billion com- binations. OPTIONS --pipe basepath Base path for the FIFO pair. BASEPATH.req and BASEPATH.resp are created here with mode 0660. The directory must exist and be writable by the operator. Default: /run/aishell-gate/confirm --timeout secs Per-request timeout. If the operator does not respond within this many seconds, aishell-gate-confirm forwards an empty response to aishell-gate-exec, which treats it as a refusal and skips the action. The relay itself remains armed and continues waiting for further requests; only the individual unanswered request is treated as refused. Default: 120. Set to 0 to disable (legacy behaviour; not recommended). Raise this while first setting up a deployment. 120 seconds is sized for an operator already watching the relay window, not for one who is still switching terminals, reading the request for the first time, and deciding what to type. --timeout 600 is a reasonable value during setup and testing. An expired request is reported on the operator's terminal ("read timed out ... treating as refusal") and on the AI side as a closed or unreadable response pipe. The AI-side wording de- scribes the pipe rather than the timeout, so a slow confirmation can look like a plumbing failure. Check the operator's terminal before investigating the FIFOs. --help | -h Print a usage summary and exit. ONE-TIME SETUP The following setup is performed once on the target machine as root. It installs the relay for the operator, creates the shared aishell-gate Unix group, and creates a runtime directory with the setgid bit and a systemd-tmpfiles(8) drop-in so the directory survives reboots. Who is "operator" Two accounts are involved and only one of them is new. ai-agent is the restricted account the AI connects to over SSH under a forced command. The operator is whichever ordinary human account approves confirma- tions -- if you log in as jsmith, then jsmith is the operator. Substi- tute your own login name wherever this page writes "operator". There is no need to create an account by that name. The split is the security property. The ai-agent account is deliber- ately unable to reach a terminal, so it cannot approve its own commands; the FIFO pair is the only channel between the two, and the human's pro- cess owns it. Install the relay aishell-gate-confirm is run by the operator, not by ai-agent, so it must be on the operator's PATH. It ships in the package bin/ directory as aishell-gate-confirm.sh; install it without the extension to match this page and the deployment guides: install -m 755 /bin/aishell-gate-confirm.sh \ /usr/local/bin/aishell-gate-confirm command -v aishell-gate-confirm Do not run it out of the ai-agent account's home directory. That directory is writable by the AI's account, and the relay is the tool the human uses to approve the AI's commands. Shared group and runtime directory Create the aishell-gate group and add both accounts: groupadd aishell-gate usermod -aG aishell-gate operator usermod -aG aishell-gate ai-agent Create the runtime directory with the aishell-gate group and the set- gid bit. New files inherit the aishell-gate group automatically: mkdir -p /run/aishell-gate chown root:aishell-gate /run/aishell-gate chmod 2770 /run/aishell-gate Make the directory survive reboots (/run is a tmpfs on virtually all modern Linux systems and is recreated fresh on every boot): echo 'd /run/aishell-gate 2770 root aishell-gate -' \ > /etc/tmpfiles.d/aishell-gate.conf systemd-tmpfiles --create /etc/tmpfiles.d/aishell-gate.conf On non-systemd systems, add the mkdir, chown, and chmod commands to /etc/rc.local or the equivalent early-boot script so the directory is recreated before sshd starts on each boot. Verify the result: ls -ld /run/aishell-gate # drwxrws--- root aishell-gate /run/aishell-gate New group membership requires a fresh login A process receives its group list at login and never revises it. An operator session that predates the usermod above still carries the old list, and aishell-gate-confirm will fail to create the FIFOs with a permission error that reads like a software fault. Distinguish the two cases: getent group aishell-gate # the database: lists operator, ai-agent id # this session: shows aishell-gate? If getent lists the account but bare id does not, the change succeeded and the session is stale. Log out fully and back in. On a desktop, closing terminal windows is not enough: every terminal inherits its groups from the graphical login session, so the desktop session itself must be restarted. Over SSH, close the connection and remove any ControlMaster socket under ~/.ssh, which can silently reuse the old session. To proceed in one terminal without logging out, run newgrp aishell-gate -- it starts a subshell with the group applied. This is per-terminal and does not persist. PER-SESSION OPERATOR WORKFLOW 1. SSH to the target machine in a dedicated window or tab and run aishell-gate-confirm: ssh operator@remotehost $ aishell-gate-confirm [aishell-gate-confirm] Terminal: /dev/pts/3 [aishell-gate-confirm] Req FIFO: /run/aishell-gate/confirm.req [aishell-gate-confirm] Resp FIFO: /run/aishell-gate/confirm.resp [aishell-gate-confirm] Status: armed -- waiting for confirmation requests [aishell-gate-confirm] Press Ctrl-C to disarm. 2. Keep this window open for the duration of the AI session. 3. When the AI submits a plan that triggers a confirmation, the full request appears here. Type the appropriate response (y, yes, or the challenge code). The AI's session receives the result immediately. 4. Press Ctrl-C to disarm when done. Both FIFOs are removed cleanly on exit. CONFIGURING AISHELL-GATE-EXEC Add --confirm-pipe and --confirm-lock to the forced command in authorized_keys. Invoke aishell-gate(1) rather than aishell-gate-exec directly: the wrapper performs pre-flight checks and locates the policy engine itself, so --policy-binary is not needed, and it is the stable entry point for forced commands as the system evolves. command="/usr/local/bin/aishell-gate --policy-preset ops_safe --confirm-pipe /run/aishell-gate/confirm --confirm-lock /run/aishell-gate/confirm.lock --audit-log /var/log/aishell/audit.log",no-pty,no-port-forwarding,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAA... THE ENTRY MUST BE ONE PHYSICAL LINE. It is shown unwrapped above for that reason. authorized_keys has no line-continuation syntax: a trail- ing backslash is not a continuation, it is part of the option text, and sshd parses each physical line as a separate entry. Splitting the entry across lines does not produce an error. It fails open: the frag- ment holding the key material is parsed as a valid entry with no command= option, the key authenticates normally, and the AI receives an ordinary interactive shell with the gate bypassed entirely. Verify after every edit: awk '{print NR": "substr($0,1,60)}' ~ai-agent/.ssh/authorized_keys One numbered line per authorised key. More than that means the entry is broken. Then confirm the gate is actually in the path: ssh ai-agent@host # must hang, not return a shell prompt # Ctrl-D -> "input is not a JSON object" A shell prompt here means the forced command is not being applied. Verify the path in command= against the installed location: packaged installs commonly land in /usr/local/bin, not /usr/bin. Check with `command -v aishell-gate`. Note also that command= binds to the key, so it is only applied when that key authenticates. If the ai-agent account also accepts a pass- word, password login bypasses the forced command and the gate with it. Lock it: passwd -l ai-agent The --confirm-lock flag is required alongside --confirm-pipe whenever multiple AI agents may connect simultaneously. It serialises the con- firmation phase across concurrent sessions: only one aishell-gate-exec session may write to the request FIFO and read from the response FIFO at a time. Other sessions block on an exclusive flock(2) until the cur- rent session finishes. The lock is released before any execve(2) call, so command execution across sessions still proceeds in parallel. With- out the lock, concurrent sessions would interleave their JSON frames on the shared FIFOs, producing unparseable requests. TESTING THE RELAY Verifying a new relay installation requires a command that is both ALLOWED and carries a confirmation level above none. This is a narrow target and the obvious candidates are the wrong ones. A denied command never reaches the relay. Confirmations are collected only for actions the policy engine has already allowed, so reaching for something dangerous -- rm -rf, chmod 777 -- produces a denial and an ap- parently dead relay. Equally, a harmless command such as ls sits at confirm: none and is executed without ever consulting the operator. Neither outcome indicates a fault. Preset matters more than the command. Under read_only, git is allowed only as the three read-only subcommands git status, git diff, and git log; a bare git push or git commit has no matching allow rule and is denied by default. ops_safe allows git at confirm: action. Under dev_sandbox, curl, wget, rsync, ssh, zip, unzip, and git are all allowed at confirm: action, which makes it the most convenient preset for a first relay test. A reliable probe under dev_sandbox, with the relay armed in another win- dow: echo '{"actions":[{"cmd":"curl"}]}' | ssh ai-agent@host curl with no arguments prints its own usage text, so approving it is harmless. The request should appear on the operator's terminal with the command, goal, risk score, blast radius, and policy reason, followed by the Approve? [yes/NO] prompt. To determine the confirmation level of any command without executing anything, use --dry-run-json on the executor: it reports decision and confirm level per action. See aishell-gate-exec(1). WHEN THE RELAY IS NOT ARMED If aishell-gate-confirm is not running when a confirmation fires, aishell-gate-exec will find no reader on the request FIFO and exit im- mediately with code 5: [gate-exec] ERROR: confirmation request pipe has no reader. [gate-exec] aishell-gate-confirm is not running in an operator session. [gate-exec] Start it: ssh operator@host then run: aishell-gate-confirm [gate-exec] No commands have been executed. No commands run. The AI receives exit code 5 and the audit log records the failure. The operator needs to start aishell-gate-confirm before the AI tries again. SINGLE-SESSION INTERACTIVE DEPLOYMENTS aishell-gate-confirm is not needed when a human operator runs aishell-gate-exec --interactive at a single terminal. In interactive mode, confirmations are collected directly from /dev/tty by the execu- tor. Neither --confirm-pipe nor --confirm-lock is required. If the AI genuinely needs to operate with no human in the loop, the correct approach is a policy preset -- such as read_only or a custom policy file -- that produces confirm: none for every command the AI will run. The confirmation system should then never fire. A confirma- tion requirement appearing in a fully automated session is the policy engine correctly identifying a command that warrants human review. The right response is to adjust the policy or add a human, not to bypass the system. FIFO LIFECYCLE aishell-gate-confirm creates both FIFOs with mkfifo(1) at mode 0660 in the /run/aishell-gate/ directory (setgid, group shared with ai-agent). On exit -- whether by Ctrl-C, SIGTERM, or error -- both FIFOs are removed if they were created by this invocation. A stale FIFO from a previous crashed session is replaced atomically on startup. The FIFOs are opened O_RDWR rather than O_RDONLY to avoid blocking on open: a FIFO opened O_RDONLY blocks until a writer appears. O_RDWR re- turns immediately. This is documented POSIX behaviour for FIFOs on Linux. DIAGNOSTICS The relay disarms the instant a request arrives, printing nothing Fixed in 0.15.0. Versions up to and including 0.14.0 terminated silently on every action-level and typed-level confirmation. display_request() captured the parsed field list with command substitution, which strips all trailing newlines; for a re- quest with no plan_actions array the final field is empty, its line was removed on capture, and the last read reached end-of- input. Under set -e that non-zero return terminated the script through the EXIT trap, removing the FIFOs. Nothing had failed, so nothing was printed. Only plan-level requests, which populate that field, worked. Upgrade to 0.15.0. Cannot create request FIFO / permission denied The operator's session predates its aishell-gate group member- ship. See ONE-TIME SETUP above. Relay armed but nothing arrives when the AI submits a plan The action was denied rather than allowed, or it sits at confirm: none. See TESTING THE RELAY above. Requests parse but the operator sees a half-empty block From 0.15.0 this is reported explicitly rather than dis- played: a truncated field stream (python3 killed, or a format mismatch between exec and the relay) refuses that single request and leaves the relay armed. Any shell script failing silently under set -e can be traced with `bash -x /usr/local/bin/aishell-gate-confirm 2>&1 | tee /tmp/trace.log`. The last command shown before the cleanup trap fires is the one that returned non-zero. EXIT CODES 0 Operator pressed Ctrl-C or SIGTERM; FIFOs cleaned up normally. 1 Not running in a terminal, or could not determine PTY device. 2 Could not create FIFOs or open them for read/write. 3 Usage error (unknown flag or missing argument). 4 python3 not available (required for JSON parsing of requests). Note: an unanswered confirmation request that hits --timeout does not cause aishell-gate-confirm to exit. An empty response is forwarded for that single request and the relay continues waiting. REQUIREMENTS bash 4+ The script uses bash-specific features including read -t for timeout support. A POSIX /bin/sh is not sufficient. python3 Used for JSON parsing of the confirmation request. The standard library module json is used; no third-party packages are re- quired. mkfifo Used to create the named FIFOs. Available on all Linux systems. tty(1) Used at startup to identify the operator's terminal device. Tested on Linux. Not tested on macOS or BSD (these platforms are not supported in the v1.0 release). SECURITY NOTES The operator's Unix credentials -- owning the FIFO -- are what autho- rise the confirmation response. aishell-gate-exec never opens any PTY device in the pipe path. The typed challenge code is sent only through the FIFO to the opera- tor's terminal. It never appears in the AI's SSH channel or on the AI's stderr. The executor verifies the operator's typed response indepen- dently; aishell-gate-confirm does not pre-verify it before relaying. The request JSON parsed by aishell-gate-confirm uses safe line-by-line ex- traction via python3(1) with no eval(1) call. Shell metacharacter in- jection through command content is not possible regardless of what the command contains. USAGE EXAMPLES Standard remote deployment (operator armed, waiting for requests): ssh operator@remotehost $ aishell-gate-confirm Custom FIFO base path and extended timeout: aishell-gate-confirm --pipe /var/run/mygate/confirm --timeout 300 Verify the runtime directory is correctly configured: ls -ld /run/aishell-gate stat /run/aishell-gate/confirm.req # after arming SEE ALSO aishell-gate-exec(1), aishell-gate-policy(1), mkfifo(1), flock(2), ssh(1), sshd(8) AUTHOR Sean T. Gilley VERSION aishell-gate-confirm 0.15.0 AIShell-Gate August 2026 AISHELL-GATE-CONFIRM(1)