
"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
                                                 

aishell-gate-exec(1)
====================

NAME
----
aishell-gate-exec -- execution gateway for aishell-gate-policy


BETA RELEASE
------------

This is a beta release. Do not use on production systems. Best tested
on a clean, isolated OS instance. Use --dry-run to validate policy and
confirmation configuration without executing anything.


EDITIONS
--------

AIShell-Gate is available in Standard and Enterprise editions. Run
--version to identify your edition. Features marked [ENTERPRISE] below
are not available in the standard binary; invoking them produces a clear
"not available in standard edition" message rather than a silent failure.

Standard edition includes: all policy evaluation, execution gateway, the
read_only, dev_sandbox, and ops_safe presets, all four confirmation
levels, interactive mode, single-session TTY confirmation (--confirm-tty),
relay confirmation mode for remote/multi-session deployments
(--confirm-lock, --confirm-pipe, and the aishell-gate-confirm(1) companion
tool), audit logging (JSON Lines), jail-root enforcement, session policy,
and the user-layer policy override (--policy-user).

Enterprise adds: the ci_build, ci_deploy, ci_admin, and danger_zone
presets; base-layer and project-layer policy overrides (--policy-base,
--policy-project); HMAC-SHA256 tamper-evident audit chain; --audit-verify;
and cryptographic session ID correlation.


SYNOPSIS
--------

    aishell-gate-exec [executor-flags] [policy-flags] [-- other-policy-flags]

    aishell-gate-exec --policy-binary ./aishell-gate-policy --plan plan.json

    aishell-gate-exec --policy-preset ops_safe --jail-root /work --plan plan.json

    echo '{"goal":"check repo","actions":[{"type":"command","cmd":"git status"}]}' \
      | aishell-gate-exec --policy-binary ./aishell-gate-policy --policy-preset dev_sandbox


DESCRIPTION
-----------

aishell-gate-exec is the execution harness that sits between a JSON action
plan (from an AI agent or operator) and the Unix system. It has no policy
logic of its own. Its job is to:

  Interactive mode (--interactive or auto-detected when stdin is a TTY):
    Reads commands one at a time from /dev/tty, assesses each through
    the policy engine, enforces the appropriate confirmation gate, and
    executes allowed and confirmed commands. Full audit trail throughout.
    Source identity defaults to "human" unless --source is set.

  JSON plan mode (default when stdin is not a TTY):
  1. Read a structured JSON action plan from stdin or --plan FILE.
  2. Submit the plan to aishell-gate-policy for policy evaluation.
  3. Display the evaluation summary to the operator.
  4. Collect human confirmation for any action that requires it.
  5. Execute allowed and confirmed actions via execve() with a resolved
     absolute binary path and a sanitised environment.
  6. Write a structured audit trail of every step.

aishell-gate-exec enforces a strict separation between evaluation and
execution. The policy engine is a separate process invoked via fork/execve
across a hard OS boundary. The executor reads only the JSON output of that
process; it never parses the human-readable trace text that the engine also
emits.

Commands are never passed through a shell. The pre-tokenised argv[] array
from the policy engine's JSON response is passed directly to execve(),
eliminating the class of shell-injection vulnerabilities that arise when a
command string is re-evaluated at execution time.


INPUT FORMAT
------------

The input is a JSON object, read from stdin by default or from a file with
--plan. The following fields are recognised:

  goal (optional but recommended)
      Human-readable description of intent. Included in the audit log and
      displayed in the evaluation banner. Maximum 511 characters. Omitting
      it does not block evaluation or execution, but it leaves the audit
      log and any confirmation prompt without a description of intent,
      which makes both harder to follow after the fact.

  source (not read from plan)
      Source identity is a deployer-set CLI flag (--source), not a plan
      field. Any "source" field in the input JSON is silently ignored.
      This prevents an AI agent from self-reporting its own trust level.
      See --source under OPTIONS.

  strategy (optional)
      Multi-action failure policy. Values:
        "fail_fast"    Stop at the first action that exits non-zero.
                       Remaining confirmed actions are skipped and logged.
                       The first non-zero exit code is returned. Default.
        "best_effort"  Attempt all allowed and confirmed actions regardless
                       of prior failures. Returns the first non-zero exit
                       code encountered (preserves root cause, not last error).

  actions (required)
      Array of action objects. Each action may be:
        { "type": "command", "cmd": "git status" }
      or a bare string shorthand:
        "git status"
      Only type "command" is currently supported. Maximum 64 actions.

Example (with protocol version block):

  {
    "protocol": {"name": "aishell-gate-exec-input", "version": "1.0"},
    "goal": "inspect repository state",
    "source": "ai",
    "strategy": "fail_fast",
    "actions": [
      { "type": "command", "cmd": "git status" },
      { "type": "command", "cmd": "git diff --stat" }
    ]
  }

The "protocol" block is optional; absent envelopes are accepted as v1.0
(backward compatibility). Mismatched protocol names are rejected with a
clear error. Major version > 1 is rejected (requires a newer binary).


OPTIONS
-------

Executor flags:

  --policy-binary <path>
      Path to the aishell-gate-policy binary. Resolved to an absolute path
      at startup via realpath(3) and verified executable before any plan is
      read. Must contain a '/' character — bare names are rejected to prevent
      PATH-based substitution.
      Default: ./aishell-gate-policy

  --interactive
      Activate human terminal mode. Commands are read one at a time from
      /dev/tty, assessed by the policy engine, confirmed through the
      appropriate gate, and executed. Session continues until EOF or 'quit'.
      Auto-detected when stdin is a TTY and --plan FILE is not given.
      Source identity defaults to "human" when --interactive is active and
      no explicit --source flag was given.
      Meta-commands available in interactive mode:
        help      Show available meta-commands and confirmation level meanings.
        status    Session statistics: commands executed vs. denied.
        policy    Show policy engine path and source identity.
        quit/exit End the session cleanly.
      Note: aishell-gate-confirm is not used in interactive mode. Confirmations
      are collected directly from /dev/tty by the executor. aishell-gate-confirm
      is for remote deployments where the AI agent and operator are in
      separate SSH sessions.

  --dry-run-json
      Parse the input plan and run the full policy evaluation for every
      action without executing anything. Writes a structured JSON document
      to stdout at session end describing every action:
        - protocol block (aishell-gate-dry-run 1.0)
        - dry_run: true
        - goal, strategy, overall_decision
        - actions[]: idx, cmd, decision, confirm, binary (resolved path),
          argv[], risk_score, blast_radius, layer, reason

      Implies --dry-run (no execve() calls are made). Human-readable stderr
      output is suppressed when --dry-run-json is active; policy evaluation
      messages go to the audit log only. The exit code reflects what the
      executor would have done: 0 if all actions would be allowed, 1 if any
      would be denied.

      Designed for AI agents that want to inspect a plan before committing
      to live execution. aishell-gate-mcp uses this internally for the
      evaluate_plan tool.

      Example:
        cat plan.json | ./aishell-gate-exec \
          --policy-binary ./aishell-gate-policy \
          --policy-preset ops_safe \
          --dry-run-json > assessment.json

  --plan <file>
      Read the input JSON plan from file instead of stdin.

  --eval-timeout <secs>
      Kill the policy engine process with SIGKILL if evaluation takes longer
      than this many seconds. Execution fails closed on timeout.
      Default: 30. Set to 0 to disable.

  --input-timeout <secs>
      Fail if no input plan data arrives on stdin within this many seconds.
      Prevents the binary from blocking indefinitely when invoked without a
      piped plan (e.g. interactively at a terminal or from an AI that stalls
      before writing the plan). On expiry the binary emits a diagnostic to
      stderr and exits 5. Has no effect when --plan FILE is used, since file
      reads do not block.
      Default: 30. Set to 0 to disable.

  --max-response-bytes <n>
      Kill the policy engine process and fail closed if its JSON response
      exceeds n bytes. Buffer growth is capped before the limit to prevent
      memory exhaustion.
      Default: 8388608 (8 MiB). Set to 0 to disable.

  --audit-log <file>
      Write a structured JSON Lines audit log to file.
      Default: $AISHELL_EXEC_AUDIT_LOG environment variable, or
               /var/log/aishell/audit.log if the variable is unset.
      Note: this is the EXEC audit log only. It uses a different internal
      format from the policy engine audit log and can only be verified
      with --audit-verify on this binary (Enterprise edition only). See
      AUDIT LOGGING below.
      If --audit-log is given explicitly, failure to open the file is fatal.
      If using the default path, a failure produces a warning but does not
      abort execution.

  --confirm-tty <path>
      Single-session interactive use only. Read confirmation prompts from
      PATH instead of /dev/tty. Use this when running a single AI session on
      a machine you are logged into and want to redirect prompts to a specific
      device. Not suitable for multi-session remote deployments — use
      --confirm-pipe instead. When neither flag is given the default is
      /dev/tty, which works correctly in any session that has a controlling
      terminal.

  --confirm-lock <path>
      Serialise concurrent confirmation prompts. Only one aishell-gate-exec
      session may be in the confirmation phase at a time; others block on an
      exclusive flock(2) until the current session finishes and releases the
      lock. The lock is released before any execve(2) call so command
      execution across sessions proceeds in parallel. The lock file is
      created automatically if it does not exist. Required when --confirm-pipe
      is in use and multiple AI sessions may connect simultaneously.
      Default path: /tmp/aishell-gate-<uid>.lock
      The lock is only acquired when --confirm-lock or --confirm-pipe is
      explicitly set; single-session deployments using /dev/tty are not
      affected.

  --confirm-pipe <basepath>
      Secure pipe-based operator confirmation relay for remote multi-session
      deployments. BASEPATH.req and BASEPATH.resp must be named FIFOs
      created by the aishell-gate-confirm(1) companion tool, which owns them and
      runs as the operator.
      aishell-gate-exec writes a JSON request to BASEPATH.req containing the
      full command context: session_id, action index, confirm level, cmd,
      goal, source, risk_score, blast_radius, reason, and for typed
      confirmations the challenge code. aishell-gate-confirm reads it, displays
      all context on the operator's own terminal, reads the operator's
      response, and writes it back through BASEPATH.resp.
      aishell-gate-exec never opens any PTY device in this path.
      Use with --confirm-lock for multi-session safety.
      Default base path: /run/aishell-gate/confirm

  --dry-run
      Suppress all fork and execve calls. Policy evaluation, confirmation
      gates, and audit logging all run normally -- the command is assessed
      and confirmed exactly as it would be in a live session. No process is
      spawned and no command is executed.
      On startup, a banner is printed to stderr:
        [gate-exec] DRY-RUN mode: execution suppressed.
      For each allowed action, a per-action line is printed to stderr:
        [gate-exec] dry-run: would exec: <resolved-binary-path> ...
      The audit log records a DRY_RUN_SUPPRESSED event (AEV_DRY_RUN)
      in place of EXEC_START/EXEC_COMPLETE.
      Use this flag to test policy and confirmation configuration without
      side effects.

  --verbose
      Print internal diagnostics to stderr: envelope contents, response JSON,
      policy engine exit code, response byte cap, and execution path
      resolution.

  --help / -h
      Print a usage summary and exit.

  --version
      Print the version string and exit.

Common policy engine flags (no -- required):

  The six policy engine flags that appear on most invocations may be given
  directly alongside executor flags, without a -- separator:

  --policy-preset <n>
      Built-in policy preset. Standard edition: read_only, dev_sandbox,
      ops_safe (default). Enterprise edition adds: ci_build, ci_deploy,
      ci_admin, danger_zone. Forwarded to the policy engine.
      Presets replace the builtin layer cmd_allow and cmd_deny lists while
      preserving arg_rules, path_rules, and net_rules. Some presets are more
      permissive than the default (danger_zone); some are more restrictive
      (read_only). The ci_* presets are designed for unattended pipelines
      and require --jail-root for path containment. Omitting this flag uses
      the full builtin policy (ops_safe).
      Upper layers can override any preset on a per-rule basis: --policy-user
      is available in both editions; --policy-project and --policy-base
      require Enterprise edition.

  --policy-base <file>  [ENTERPRISE]
      Base policy override file. Forwarded to the policy engine.

  --policy-project <file>  [ENTERPRISE]
      Project policy override file. Forwarded to the policy engine.

  --policy-user <file>
      User policy override file. Forwarded to the policy engine.

  --jail-root <path>
      Restrict write-like commands to this path tree. Forwarded to the
      policy engine.

  --sandbox <mode>
      Sandbox mode hint: none, cwd_jail, chroot, container, userns.
      Forwarded to the policy engine.

Other policy engine flags (pass after --):

  Any flag not listed above that needs to reach the policy engine must appear
  after a -- separator. Everything after -- is forwarded verbatim.
  Maximum 64 total forwarded flags (including the six above).

  Less-common policy engine flags:
    --audit-log <file>  Policy engine audit log (separate from the executor
                        audit log written by exec's --audit-log; these two
                        logs use incompatible internal formats — never point
                        them at the same file)
    --mode <mode>       Session mode: auto, interactive, batch, daemon
    --source <name>     Taint source override

  See aishell-gate-policy(1) for the full list.


EXECUTION MODEL
---------------

Evaluation pipeline

  aishell-gate-exec submits the plan to the policy engine subprocess as a
  JSON envelope over a pipe. The subprocess receives the envelope on its
  stdin and writes its JSON decision report to stdout. aishell-gate-exec
  reads this report with a configurable deadline (--eval-timeout) and byte
  cap (--max-response-bytes). If either limit is exceeded, the subprocess is
  sent SIGKILL and execution fails closed.

  Three deadline layers protect against indefinite blocking throughout the
  pipeline:

  Input deadline (--input-timeout)
      Applied to the initial stdin read before any policy engine subprocess
      is spawned. If no plan data arrives within the timeout, the binary
      emits a diagnostic and exits 5. This prevents indefinite blocking when
      the binary is invoked without a piped plan — for example, at an
      interactive terminal, or from an AI agent that stalls before writing
      the action plan. Not active when --plan FILE is used.

  Envelope write deadline (--eval-timeout, write phase)
      When writing the JSON envelope to the policy engine's stdin pipe, the
      same eval-timeout deadline applies. If the policy engine does not read
      its stdin within the timeout — for example because it exited early,
      blocked, or was given as a bare non-compliant binary — the entire
      policy engine process group is sent SIGKILL and execution fails closed.

  Response read deadline (--eval-timeout, read phase)
      After the envelope is written, the response is read with the same
      deadline and an optional byte cap (--max-response-bytes). Both limits
      send SIGKILL to the process group and fail closed on breach.

  Process group kill
      The policy engine subprocess is placed in its own process group at
      fork time. All SIGKILL signals are sent to the entire process group
      (kill(-pgid, SIGKILL)) so that any grandchildren spawned by the policy
      engine (e.g. sub-shells or helper processes) are terminated along with
      the engine itself, preventing orphaned processes from holding pipes
      open after the engine is killed.

  The JSON report is the sole authoritative source of policy decisions.
  Any human-readable text the policy engine emits alongside the JSON is for
  operator display only and is never parsed.

Confirmation collection

  All human confirmations are collected before any command is executed.
  This prevents a partially-executed plan when an operator refuses a
  mid-sequence action. The confirmation window is serialised across
  concurrent sessions via --confirm-lock (see OPTIONS).

  Two confirmation paths are available:

  TTY path (single-session interactive use)
      Prompts are read from /dev/tty (or --confirm-tty PATH). This is
      the correct path for a single operator running a single AI session
      on a machine they are logged into. The executor opens the TTY
      device directly.

  Pipe path (remote multi-session deployments)
      When --confirm-pipe is set, the executor writes a JSON request to
      the named FIFO and reads the response from the paired FIFO.
      aishell-gate-confirm(1) owns both FIFOs and runs as the operator,
      displaying full command context on their terminal and relaying
      the response. The executor never opens any PTY device. The typed
      challenge code is sent only through the pipe — it never reaches
      the AI's stderr channel.

  Confirmation levels:
    none    No prompt; proceed immediately.
    plan    "Proceed? [y/N]" — review the step before execution.
    action  "Approve? [yes/NO]" — explicit operator approval required.
    typed   Command-specific challenge must be typed back exactly.

  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. The challenge changes with every unique command,
  proving the operator read the specific command being authorised rather than
  muscle-memorying a fixed word. 31^8 ≈ 852 billion combinations.

Execution

  Each allowed and confirmed command is executed via fork+exec. All plans
  use fork+wait so the parent process always survives to write closing audit
  entries (EXEC_COMPLETE, SESSION_END) before exiting. The parent then exits
  with the child's exit code, so the exit code visible to the caller is
  identical to the child's.

  Absolute binary path
      The command name is resolved against a compile-time safe path list:
        /usr/local/bin, /usr/bin, /bin, /usr/sbin, /sbin
      The inherited $PATH is never consulted. A binary not found in the
      safe path list fails with exit code 6 rather than silently proceeding.

  Sanitised environment
      A minimal environment is constructed from an explicit allowlist of
      variables: HOME, USER, LOGNAME, TERM, COLORTERM, LANG, LC_ALL,
      LC_CTYPE, LC_MESSAGES, LC_TIME, LC_NUMERIC, LC_COLLATE, TZ, TMPDIR.
      PATH is always set to the safe path list above. All other variables
      from the calling environment are silently dropped, including
      LD_PRELOAD, DYLD_INSERT_LIBRARIES, PYTHONPATH, GIT_EXEC_PATH, and
      any other interpreter or dynamic-linker injection vectors.

  Pre-tokenised argv[]
      The argv[] array comes directly from the policy engine JSON response.
      aishell-gate-exec never re-parses or re-splits a command string at
      execution time.

  Multi-action plans
      Actions are run sequentially. The strategy field controls behaviour
      on non-zero exit: fail_fast stops at the first failure and logs how
      many confirmed actions were skipped; best_effort continues through
      all actions and returns the first non-zero exit code encountered.


STARTUP SECURITY CHECKS
-----------------------

Two checks run at startup, before argument parsing and before any file is
opened:

  Setuid / setgid refusal
      If the real and effective UID (or GID) differ, the OS elevated this
      process via the setuid or setgid bit. aishell-gate-exec refuses to
      run with borrowed privileges because the confirmation UI reads from
      /dev/tty, environment variables affect the safe path search, and any
      bug in the executor binary becomes a local privilege escalation path.
      Fix: chmod u-s aishell-gate-exec (or chmod g-s).

  Binary writable by group or others
      If an attacker can overwrite the executor binary, the policy engine
      is never consulted and policy is bypassed entirely. The binary's
      permissions are checked via stat(). On Linux, /proc/self/exe is used
      to obtain the real path, immune to argv[0] spoofing. If the stat
      fails, a warning is emitted and this check is skipped.
      Fix: chmod go-w aishell-gate-exec.

A security violation fails with exit code 5. If an audit log is accessible,
a SECURITY_VIOLATION event is written before exit.


AUDIT LOGGING
-------------

aishell-gate-exec writes one JSON Lines entry to the audit log per event.

[ENTERPRISE] Entries are HMAC-SHA256 chained: each record contains the
HMAC of the previous entry, so a gap or modification is detectable by
any holder of the key. The standard edition writes the same JSON Lines
entries but without the HMAC chain.

TWO-LOG ARCHITECTURE
  aishell-gate uses two separate, independent audit logs:
    exec log:   written by this binary; format identified by "chain_hmac" field
    policy log: written by aishell-gate-policy; format identified by "entry_hash"
  These logs use incompatible internal formats and are verified by different
  binaries. Do not mix them:
    aishell-gate-exec --audit-verify FILE   (exec logs only) [ENTERPRISE]
    aishell-gate-policy --audit-verify FILE (policy logs only)
  Passing a policy log to exec's --audit-verify (or vice versa) will
  produce an error or an incorrect broken-chain result.
  The policy engine has no default audit log path. Its log is only written
  when --audit-log is explicitly passed to aishell-gate-policy.

Log path (checked in this order):
  1. --audit-log FILE command-line flag
  2. AISHELL_EXEC_AUDIT_LOG environment variable
  3. /var/log/aishell/audit.log

HMAC key (checked in this order):
  1. AISHELL_AUDIT_KEY environment variable (64 ASCII hex characters = 32 bytes)
  2. /etc/aishell/audit.key (64 ASCII hex characters on the first line)
  3. Per-session random key from /dev/urandom (ephemeral; chain cannot
     be verified across sessions in this mode — a warning is emitted to
     stderr at startup; the warning text contains the word "ephemeral"
     to aid grep-based monitoring)

Key file format (exec only):
  The exec HMAC key file contains 64 ASCII hex characters (representing
  32 raw bytes of key material) on the first line. Generate with:
    dd if=/dev/urandom bs=32 count=1 | xxd -p | tr -d '\n' \
      > /etc/aishell/audit.key && chmod 640 /etc/aishell/audit.key
  WARNING: aishell-gate-policy uses a different key file format (64 raw
  binary bytes). The two key files are NOT interchangeable.

Concurrent write safety: flock(LOCK_EX) is acquired on the log file
descriptor before each write. The HMAC chain anchor and sequence counter
are updated while the lock is held to prevent a forked chain if two
processes race on the same log file.

Multi-session audit note: each aishell-gate-exec invocation is an
independent process with its own in-memory chain state. When multiple
sessions write to the same log file, the file contains one internally
consistent HMAC chain per session_id, not a single global sequence.
Verify chains per session_id; treating the entire log as a single linear
sequence will produce false verification failures when sessions are
concurrent.

Event types written to the audit log:

  SESSION_START          Executor started; policy engine path, version,
                         key mode (persistent or ephemeral).
  SESSION_END            Session closed; exit code, actions executed,
                         policy-denied count.
  PLAN_RECEIVED          Goal, source, strategy, action count.
  POLICY_DECISION        Per-action: cmd, allow/deny, reason, layer,
                         risk score, policy version.
  CONFIRMATION_REQUESTED Operator prompted for a specific action.
  CONFIRMATION_RESULT    Whether the operator approved or refused.
  EXEC_START             Command about to be exec'd; resolved absolute path.
  EXEC_COMPLETE          Command finished; exit code.
  EXEC_DENIED            Execution blocked at the executor level (empty argv,
                         binary not found in safe path).
  DRY_RUN_SUPPRESSED     Emitted when --dry-run is active in place of
                         EXEC_START/EXEC_COMPLETE. Records the resolved
                         binary path that would have been executed.
  SECURITY_VIOLATION     Startup security check failed, or policy engine
                         response was missing required fields.


ENVIRONMENT
-----------

AISHELL_EXEC_AUDIT_LOG
    Default exec audit log path when --audit-log is not specified.
    Setting this variable is treated as an explicit audit request;
    failure to open the path is fatal. This variable controls ONLY the
    exec audit log. The policy engine has no equivalent env var; its
    log path is set via --audit-log passed directly to aishell-gate-policy.

AISHELL_AUDIT_KEY
    64-character ASCII hex HMAC key for the exec audit chain.
    Takes priority over /etc/aishell/audit.key.
    Format: exactly 64 lowercase hex characters (= 32 raw bytes).
    This format differs from the policy engine key file (64 raw binary bytes).


EXIT STATUS
-----------

  0   All actions allowed, confirmed, and executed (all exited 0).
  1   Policy DENY on one or more actions; no commands were executed.
  2   Human confirmation refused; no commands were executed.
  3   Policy engine process error (spawn failed, timeout, byte limit,
      killed by signal, or bad exit code from the policy engine).
  4   JSON parse error (input plan or policy engine response).
  5   Usage or argument error, startup security check failed, or
      confirmation lock/pipe could not be opened.
  6   Gateway could not execute a confirmed ALLOW: empty argv from the
      policy engine, binary not found in the safe path list, or a
      fork/chdir/execve 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 is printed
      to stderr as "[gate-exec] action N exited M" and recorded in the
      audit log.
  8   The command ran and the GATEWAY killed it for exceeding
      --action-timeout. Separate from 7 because the right response
      differs: a failed command may be worth fixing and re-running, a
      command the gate had to kill should not be retried unchanged.
  128+N
      Execution was interrupted, or a command killed from outside, by
      signal N.

  Codes 1-6 describe what the GATEWAY decided or failed to do. They never
  carry a command's own exit status. This separation is deliberate: before
  0.57.0 the executor returned the command's exit status directly, so
  `rm nofile` (exit 1) was indistinguishable from a policy denial. A caller
  acting on exit codes -- an AI agent especially -- would conclude policy had
  blocked a command policy had in fact allowed, and rewrite it to satisfy a
  rule that never objected. Scripts that tested for a specific command exit
  status through the gate must now read the "action N exited M" stderr line
  or the audit log instead.


AISHELL-GATE-CONFIRM (CONFIRMATION RELAY)
-------------------------------------

aishell-gate-confirm is the companion operator-side relay used in remote
multi-session deployments, where aishell-gate-exec has no controlling
terminal on the AI's SSH connection. It is available in both editions
and is used together with --confirm-pipe and --confirm-lock above. It is
not needed for interactive use at a single terminal, which uses
--confirm-tty. Full design and setup is in the Remote
Deployment Guide; this section is the flag and exit-code reference.

  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).

  --help | -h
      Print a usage summary and exit.

  Exit codes (aishell-gate-confirm itself, distinct from aishell-gate-exec's
  exit codes above)

  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.


BUILD
-----

AIShell-Gate is distributed as pre-compiled binaries. Source code is
available to organisations conducting a security review or audit.
Contact info@aishellgate.com to request access.

Standard:
    cc -std=c11 -Wall -Wextra -O2 -o aishell-gate-exec aishell-gate-exec.c

Enterprise:
    cc -std=c11 -Wall -Wextra -O2 -DENTERPRISE -o aishell-gate-exec aishell-gate-exec.c

No external libraries are required. JSMN (JSON tokenizer) and SHA-256 areembedded. pipe2(2) is used for O_CLOEXEC pipe creation; it is a Linux
extension available on Linux 2.6.27+ and glibc 2.9+. Requires a
POSIX.1-2008 compatible system.


USAGE EXAMPLES
--------------

Human operator interactive mode (auto-detected when stdin is a TTY):

    ./aishell-gate-exec --policy-preset ops_safe

    # Or with explicit flag:
    ./aishell-gate-exec --interactive --policy-preset ops_safe \
        --audit-log ./exec.jsonl

    # Sample session:
    # exec> git status        <- ALLOW, confirm=none, executes immediately
    # exec> rm -rf /tmp/old   <- ALLOW, confirm=typed, challenge code presented
    # exec> quit              <- ends session, audit closed

Learn about a command before running it (policy educational mode):

    ./aishell-gate-policy --policy-preset ops_safe
    # policy> chmod +s /usr/bin/myapp
    #   ALLOW  chmod +s /usr/bin/myapp
    #   Confirmation required: typed  (gateway requires typed challenge code)
    #   Flag analysis:
    #     [danger] +s  --  set suid/sgid bit; creates privilege escalation vector
    #   To execute through the gateway:
    #     echo 'chmod +s /usr/bin/myapp' | aishell-gate-exec --source human

Run a JSON plan from stdin:

    echo '{
      "goal": "check repository status",
      "actions": [{"type":"command","cmd":"git status"}]
    }' | ./aishell-gate-exec --policy-binary ./aishell-gate-policy

Read plan from a file with a read-only preset:

    ./aishell-gate-exec --policy-binary ./aishell-gate-policy \
        --plan plan.json --policy-preset read_only

Multi-action plan with best_effort strategy and exec audit log:

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

Pass a project policy file and a jail root:  [ENTERPRISE]

    ./aishell-gate-exec --plan plan.json \
        --policy-project ./aishell-gate-policy_project.json \
        --jail-root /home/ci/workspace

Custom evaluation timeout:

    ./aishell-gate-exec --eval-timeout 10 --plan plan.json

Strict input deadline (fail fast if AI stalls before writing the plan):

    ./aishell-gate-exec --input-timeout 5 --policy-binary ./aishell-gate-policy

Pass a less-common policy engine flag (-- required):

    ./aishell-gate-exec --plan plan.json --policy-preset ops_safe -- --mode batch

Verbose diagnostics for troubleshooting:

    ./aishell-gate-exec --verbose --plan plan.json

Remote multi-session deployment (operator runs aishell-gate-confirm in a
second SSH session):

    ./aishell-gate-exec --policy-binary /usr/bin/aishell-gate-policy \
        --policy-preset ops_safe \
        --confirm-pipe /run/aishell-gate/confirm \
        --confirm-lock /run/aishell-gate/confirm.lock \
        --audit-log /var/log/aishell/audit.log \
        --plan plan.json

Inspect a plan without executing (machine-readable assessment):

    cat plan.json | ./aishell-gate-exec \
        --policy-binary ./aishell-gate-policy \
        --policy-preset ops_safe \
        --dry-run-json | python3 -c "
import json,sys
d=json.load(sys.stdin)
for a in d['actions']:
    print(a['decision'], a['cmd'], '—', a.get('reason',''))
"

Print version:

    ./aishell-gate-exec --version


LIMITS
------

Maximum actions per plan:    64
Maximum policy engine flags:    64
JSON token budget:         4096 tokens per parse
Maximum goal length:        511 characters
Maximum command length:    4095 characters
Typed challenge length:       8 characters (FNV-1a derived, 31^8 combinations)


DESIGN NOTES
------------

AIShell Labs LLC — we are an integrity company.

aishell-gate-exec deliberately has no policy logic. Policy belongs entirely
to aishell-gate-policy, which is a separate binary evaluated in a separate
process. This separation means the executor can be audited for a single
concern: does it faithfully interpret and execute what the policy engine
decided, with correct confirmation and a clean environment?

The JSON channel between the two programs is the trust boundary.
aishell-gate-exec trusts the JSON decision report produced by the policy
engine subprocess and nothing else. It does not accept policy overrides from
the calling environment, from the input plan, or from any source other than
that report.

The two binaries serve two user populations:

  AI agents: submit a JSON plan; the executor gates it, collects human
  confirmation, and executes allowed commands.

  Human operators: run aishell-gate-exec --interactive (or just run it
  at a terminal). Commands are read from /dev/tty, assessed, confirmed
  at the appropriate gate, and executed. The same flag catalog reasoning
  and confirmation machinery used for AI plans applies equally to every
  human-typed command. The audit trail is identical.

Confirmations are collected in a single pass before any command runs. This
makes the operator's intent atomic: either everything is confirmed and then
executed, or nothing is executed. A mid-plan refusal cannot leave the system
in a partially-modified state that the operator did not consciously authorise.

In interactive mode, confirmations are collected from /dev/tty directly,
not from stdin. This keeps stdin available for any automation wrapper while
the human operator handles confirmation at their terminal.

All execution uses fork+wait so that the parent process always survives to
write closing audit entries. The exit code visible to the caller is the
child's exit code propagated through the parent's return value.

The exec audit log is independent of the policy engine audit log. The
policy engine records what it evaluated and decided (entry_hash chain format);
aishell-gate-exec records what it confirmed and executed (chain_hmac format).
Together they provide end-to-end traceability from intent through evaluation,
confirmation, and execution. Each log is verified only by the binary that
wrote it; see AUDIT LOGGING for details.


SEE ALSO
--------

aishell-gate-policy(1)  -- policy evaluation engine
aishell-gate-mcp(1)     -- MCP server exposing evaluate_plan and execute_plan
aishell-gate-confirm         -- operator confirmation relay; flags and exit codes
                           above, design and setup in the Remote Deployment
                           Guide
execve(2)               -- used for all command execution
flock(2)                -- advisory locking used by the audit subsystem and
                           confirmation serialisation lock
fnmatch(3)              -- used internally by policy engine rule evaluation


AUTHOR
------

Sean T. Gilley


VERSION
-------

Run each component with --version for current version information:

    aishell-gate-exec --version
    aishell-gate-policy --version
    aishell-gate-confirm --version
