"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-policy(1)
====================

NAME
----
aishell-gate-policy -- deterministic policy engine for AI-generated shell commands


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

This is a beta release. Do not use on production systems. Best tested
on a clean, isolated OS instance. Use --dry-run (on aishell-gate-exec)
to validate policy 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, the read_only, dev_sandbox,
and ops_safe presets, confirmation levels, interactive mode, audit logging
(JSON Lines), jail-root enforcement, session policy, the user-layer policy
override (--policy-user), and --dump-standard-template.

Enterprise adds: the ci_build, ci_deploy, ci_admin, and danger_zone presets;
base-layer and project-layer policy overrides (--policy-base and
--policy-project); and HMAC-SHA256 keyed audit chain verification via
--audit-verify.


SYNOPSIS
--------

    aishell-gate-policy [options] [input-file]

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

    aishell-gate-policy --policy-preset read_only request.json

    aishell-gate-policy --audit-key audit.key --audit-verify /var/log/ag.jsonl

Generate editable policy template:

    aishell-gate-policy --dump-standard-template \
      | sed '1,/^{$/{ /^{$/!d }' > _base.json

Interactive mode (educational — does not execute):

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

With executor (aishell-gate-exec):

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


DESCRIPTION (Short)
-------------------

aishell-gate-policy evaluates shell commands or structured JSON requests and
decides whether they are permitted under a configurable policy.

It does NOT execute commands.

It is the policy engine half of a two-component system. The companion
executor, aishell-gate-exec(1), handles plan intake, human confirmation, and
execve() once the policy engine has issued an ALLOW decision.


FULL DESCRIPTION
----------------

aishell-gate-policy sits between AI systems (or humans) and Unix execution,
providing deterministic validation, risk classification, and policy
enforcement before any command runs.

The separation of evaluation from execution is a core security property,
not a convenience. aishell-gate-policy has no ability to execute anything.
aishell-gate-exec has no policy logic. An executor that is compromised cannot
grant itself permission to run a command that the policy engine has denied, because
the permission decision lives in a separate process across a hard OS boundary.

This tool follows several core principles:

  - Validate-only by design
  - Default deny
  - Explainable decisions
  - Human-first UX
  - Machine-readable output
  - No shell evaluation
  - Explicit confirmation levels
  - Audit-first mindset

It does not replace Unix permissions or security models. It provides an
explicit, policy-governed decision layer before execution occurs.

Core concepts:

Sessions
  Each invocation represents a session with identity (uid/gid), working
  directory, environment constraints, and mode (interactive, batch, daemon).
  SSH session detection is automatic via environment variables. Session
  policy can gate evaluation entirely on uid/gid allowlists, username
  allowlists, TTY state, session mode, and time-of-day -- before any
  command rule is evaluated.

Policies
  Rules defining what is allowed or denied. Policies are layered:
  built-in defaults (or a selected preset), optional base overrides,
  optional project overrides, and optional per-user overrides. Authority
  runs from lowest (built-in defaults) to highest (per-user). Base and
  project overrides require Enterprise edition; Standard edition supports
  the built-in/preset layer and the user layer only. The policy
  engine scans layers highest-authority first and stops at the first
  layer that produces any matching rule -- allow or deny. The winning
  layer's decision is final; lower-authority layers are not consulted
  once a higher-authority layer has matched. Within the winning layer,
  deny rules are evaluated before allow rules, so a layer that contains
  both a deny and an allow matching the same command will deny.

  This precedence means higher-authority layers can fully override lower
  ones, including by producing an allow where a lower-authority layer
  would have denied. If organization-wide denies must be unoverridable
  from higher layers, place them in a layer that sits at or above every
  layer that could produce a conflicting allow -- typically the user
  layer for single-operator deployments, or by restricting which layers
  operators can modify in multi-tenant deployments.

  Unknown top-level keys in a policy file are rejected immediately (not
  silently ignored) to catch typos like "cmd_denny". Unknown session
  sub-keys are also rejected, preventing silent no-ops from mistyped
  policy fields like "deny_sssh".

Decisions
  Every evaluation results in either ALLOW or DENY. There is no WARN state.
  Confirmation levels govern how much human review is required for allowed
  commands.

Confirmation levels
  none    -- no confirmation required; proceed immediately
  plan    -- show the plan before executing; human review suggested
  action  -- explicit per-command human confirmation required
  typed   -- human must type out a specific phrase to confirm

  Confirmation levels are set by policy rules and raised (never lowered)
  by risk classification. An allowed command with a high risk score will
  have its confirmation level upgraded automatically. Escalation is
  strictly monotonic: a command confirmed at "typed" cannot be downgraded
  by a lower risk score.

Risk classification
  Commands are scored 0-100 and tagged with risk flags:
    destructive          -- modifies or deletes data irreversibly
    exfiltration         -- transfers data off-system
    privilege_escalation -- attempts to gain elevated access
    persistence          -- modifies scheduling, services, or startup
    scan                 -- probes network or system topology

  Score is computed in two steps. First, risk_classify() assigns a base
  score from the command catalog and applies argument modifiers:
    +10  recursive flag on a destructive command
    +10  --force with rm or mv
    +15  any argument targets a system path (/etc, /usr, /boot, etc.)
    +10  curl or wget called with a URL argument
  Score is capped at 100 and floored at 0.

  Second, risk_apply_confirmation() applies escalation thresholds:
    score >= 40  ->  confirm raised to at least plan
    score >= 70  ->  confirm raised to at least action
    score >= 90  ->  confirm raised to at least typed

  Blast radius is also assigned: none, single, tree, system, or unknown.
  "system" is set when a root or system path is targeted; "tree" when
  recursive; "single" otherwise.

IO classification
  Each command is classified as read, write, mixed, net, exec, or unknown.
  This is recorded in the audit log and JSON output and may influence sandbox
  guidance provided to the executor.

Command catalog
  An internal catalog of Unix commands provides base risk scores,
  risk flags, and minimum confirmation floors. The catalog covers 346
  commands across categories: observe, read_text, write_fs, overwrite,
  destructive, priv, persist, network, scan, pkg, interpreter, and build.
  Representative catalog scores:
    ls, cat, grep     -- score 0-5,  confirm none
    git               -- score 20,   confirm plan
    curl, wget        -- score 60,   confirm action
    rm                -- score 80,   confirm typed
    dd, parted, fdisk -- score 95,   confirm typed
    mkfs, wipefs      -- score 98,   confirm typed
  Policy rules may impose stricter requirements than the catalog; catalog
  values are a floor, not a ceiling. Commands not in the catalog start at
  score 0, flags 0, confirm none, and are governed by policy rules alone.

Flag catalog
  For 295 of the 346 catalogued commands, individual flags are assessed with
  explicit dispositions. The flag catalog is a friction layer, not an access
  control gate: it never denies commands, only raises the confirmation level
  required and documents the reason.

  Dispositions:
    FLAG_ALLOW   -- flag is known-safe; no confirmation escalation.
                    Presence actively reduces friction on known-safe invocations.
    FLAG_WARN    -- flag meaningfully changes the risk profile; confirmation
                    raised to CONFIRM_ACTION. Every entry carries a reason in
                    the format "what the flag does; why it matters".
    FLAG_DANGER  -- flag is particularly dangerous; confirmation raised to
                    CONFIRM_TYPED. Reason is the specific threat vector.
    FLAG_UNKNOWN -- flag not in the catalog. The system reports honestly:
                    "risk unassessed, not evaluated as safe." Confirmation
                    raised to CONFIRM_ACTION as a precaution.

  Incompleteness is safe by design. FLAG_UNKNOWN is the catch-all default.
  The catalog exists to reduce friction on known-safe flags and label known
  risks. Every gap is self-reported via FLAG_UNKNOWN rather than silently
  allowed or silently escalated.

  51 commands have has_flag_catalog=false (interpreters, specialist compilers,
  trivial commands). Their exclusion is explicitly documented in the source.

  Catalog statistics:
    Commands with flag catalog:  295
    Commands without:             51  (documented rationale for each)
    Total flag entries:         3296
      FLAG_ALLOW:               1498
      FLAG_WARN:                1504
      FLAG_DANGER:               294

  Flag assessment appears in:
    Interactive mode output: [safe] / [warn] / [danger] / [unassessed] labels
    JSON output: "flag_assessment" object with "highest", "known_risky",
                 "unknown", and "note" fields on every action
    Audit log: FLAG_UNKNOWN tokens recorded for operational review


  The engine can emit sandbox mode hints and resource limits for the
  downstream executor. These are advisory only -- the engine does not enforce
  them at the kernel level. The one active exception is cwd_jail: when a
  jail root is configured, path arguments for write-like commands are
  evaluated against the jail root during policy evaluation. See SANDBOX
  OPTIONS.

Taint tracking
  All input is marked tainted on arrival. A result is untainted only after
  the engine has completed evaluation and issued an ALLOW decision. Taint
  status is reported in JSON output and the audit log.

Loopback/localhost warnings
  On the ALLOW path, network targets are scanned for loopback addresses.
  A graded warning is emitted when a command targets localhost or a
  known AI inference API port. See EVALUATION ORDER step 10.

Presets
  Built-in policy bundles for common workflows. See PRESET MODES.

Audit
  Structured append-only logging of every evaluation in JSON Lines format
  with tamper-evident hash chaining. Each entry is linked to the previous
  by a SHA-256 (or HMAC-SHA256) hash. See AUDIT LOGGING.

Busy summary
  Each evaluation produces a 3-5 line plain-text summary (busy_summary_text)
  suitable for display to a human or inclusion in an LLM context window.


MODES OF OPERATION
------------------

Interactive (human)

  Run with no input file when stdin is a TTY. Commands are typed one per line.

    > rm -rf /
    DENY: destructive command denied: rm

  Output includes: source -> normalized -> decision -> rule -> reason ->
  suggestion -> trace.

  Interactive special commands:
    (empty line)       no-op
    quit or exit       terminate
    json or :json      print full JSON for the previous command

JSON input (AI / automation)

  Provide a JSON envelope via file argument or stdin. Input must start with
  a '{' character. Output is structured JSON.

  Example envelope:

    {
      "goal": "check repository state",
      "source": "ai",
      "actions": [
        "git status",
        { "type": "command", "cmd": "git diff" }
      ]
    }

  The "goal" field is required. The "actions" array is required.
  Each action may be a bare string (treated as type=command) or an object
  with "type" and "cmd" fields. Only type=command is currently supported.
  The "source" field is optional (ai|human|broker|scheduled; default ai).

  Output JSON includes per-action: decision, confirm, layer, reason, argv,
  risk score/flags/blast_radius, busy_summary_text, taint status, and
  suggestions on deny. The envelope also includes session metadata and
  policy_sources.

Raw command mode (batch)

  Pipe or pass a single command line that does not start with '{'.
  Output is human-readable by default. Use --json to get JSON output.

    echo "git status" | aishell-gate-policy

Chain verification mode  [ENTERPRISE]

  Use --audit-verify to validate the hash chain of an existing log file.
  The program prints a per-line report to stdout and exits without reading
  any command input.

    aishell-gate-policy --audit-verify /var/log/ag.jsonl
    aishell-gate-policy --audit-key audit.key --audit-verify /var/log/ag.jsonl

  Exit codes for --audit-verify: 0 = chain intact, 1 = tampering detected,
  2 = file open or read error.


PRESET MODES
------------

Presets replace the builtin layer cmd_allow and cmd_deny lists with a named
set. The arg_rules, path_rules, and net_rules of the builtin layer are
preserved unchanged. Project and user override files still apply on top of
any preset via the hierarchical evaluation order (user > project > base >
builtin) -- project requires Enterprise edition; user is available in both
editions. Omitting --policy-preset uses the full builtin policy, which is
equivalent to ops_safe. Some presets are more permissive than the default;
others are more restrictive.

read_only, dev_sandbox, and ops_safe are available in both editions.
ci_build, ci_deploy, ci_admin, and danger_zone require Enterprise edition
(see EDITIONS).

read_only (aliases: readonly)
  Inspection-only. Read and observe commands are allowed. All write, delete,
  permission-change, and privilege commands are denied. Intended for audit
  or diagnostic sessions.

  Allowed examples: ls, cat, grep, find, ps, df, git status, git log,
  sha256sum, stat, head, tail, rg.

  Denied examples: rm, mv, cp, dd, mkfs, sudo, tee, chmod, chown.

dev_sandbox (aliases: dev)
  Developer workflow. Allows common build tools, compilers, and package
  managers. Blocks disk destructors and privilege escalation. Write operations
  are still subject to writable_dirs constraints if configured.

  Allowed examples: ls, cat, grep, find, git, make, cmake, ninja, cc, clang,
  gcc, python, python3, pip, cargo, go.

  Denied examples: dd, mkfs, mount, umount, sudo.

ops_safe (aliases: ops, default)
  Conservative operational profile. This is the default. Allows a small set
  of read-only and repo-inspection commands. Denies shells, interpreters,
  destructive tools, and privilege escalation.

  Allowed examples: ls, uname, df, ps, git status, git diff.

  Denied examples: rm, dd, mkfs, chmod, chown, sudo, sh, bash, zsh, python,
  perl, ruby, git config.

ci_build (aliases: ci)  [ENTERPRISE]
  Unattended build and test pipeline. All allowed commands carry
  CONFIRM_NONE — confirmation prompts would hang a headless CI runner.
  Requires --jail-root to constrain file operations. Requires batch mode.

ci_deploy (aliases: 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 denied. Requires batch mode.

ci_admin (aliases: 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 (aliases: danger)  [ENTERPRISE]
  Minimal restrictions. A wildcard allow rule permits most commands. The
  base deny list (shells, interpreters, privilege escalation, destructive
  disk tools) still applies. Risk classification and confirmation levels
  are still enforced. Use with caution.


OPTIONS
-------

General options:

  --help / -h
      Show usage summary and exit.

  --help-policy
      Print the policy file format reference (rule schemas, confirm levels,
      session keys, evaluation order, and an example policy file) and exit.

  --version / -V
      Print version string and build info and exit.

  --policy-preset <n>
      Select a built-in policy preset. Valid names in Standard edition:
      read_only (readonly), dev_sandbox (dev), ops_safe (ops, default).
      Enterprise edition adds: ci_build (ci), ci_deploy (deploy),
      ci_admin (admin), danger_zone (danger). See PRESET MODES and EDITIONS.
      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 builtin policy (danger_zone allows
      most commands); some are more restrictive (read_only allows only
      read and observe commands). Omitting --policy-preset uses the full
      builtin policy, which is equivalent to ops_safe. Upper policy layers
      can override any preset on a per-rule basis via the hierarchical
      evaluation order: --policy-user is available in both editions;
      --policy-project and --policy-base require Enterprise edition.

  --mode <m>
      Override session mode detection. Values: auto, interactive, batch,
      daemon. Default is auto (TTY detection).

  --source <s>
      Set the provenance label for raw or interactive input.
      Values: ai, human, broker, scheduled. Default is ai.
      Legacy aliases accepted: raw->ai, envelope->broker, web->scheduled.

  --json
      In raw command mode, output full JSON instead of human text.

  --deterministic
      Force C locale and UTC timezone for reproducible output.

  --verbose
      Enable verbose diagnostic logging to stderr.

  --dump-standard-template
      Emit the builtin policy layer as a ready-to-edit JSON override file
      and exit. The output is valid JSON (with a plain-text header block
      before the opening brace explaining the schema). Strip the header to
      produce a clean file:

        aishell-gate-policy --dump-standard-template \
          | sed '1,/^{$/{ /^{$/!d }' > _base.json

      Combine with --policy-preset to template any preset:

        aishell-gate-policy --policy-preset read_only --dump-standard-template \
          | sed '1,/^{$/{ /^{$/!d }' > read_only_base.json

      The output file may be used directly as a --policy-base, --policy-project,
      or --policy-user override file without any reformatting (--policy-base
      and --policy-project require Enterprise edition; --policy-user is
      available in both editions). All rule arrays
      from the builtin layer are included (cmd_allow, cmd_deny, arg_rules,
      path_rules, net_rules). The header documents the full schema including
      optional keys and the _replace flags.

      Intended as a starting point for custom policy authoring. Rather than
      writing override rules from scratch, operators can dump the builtin
      layer, edit the relevant entries, and load the result as an override.

  --test-plan <file>  [ENTERPRISE]
      Read a JSON test suite file and evaluate each test case against the
      active policy. Reports PASS or FAIL per case. Exits 0 if all tests
      pass, 1 if any fail, 2 on file/parse error.

      Test file format:
        {
          "tests": [
            { "cmd": "git status",  "expected": "allow", "label": "git ok"  },
            { "cmd": "rm -rf /",    "expected": "deny",  "label": "rm deny" },
            { "cmd": "make",        "expected": "allow",  "preset": "dev_sandbox" }
          ]
        }

      Fields per test case:
        cmd      -- command to evaluate (required)
        expected -- "allow" or "deny" (required)
        label    -- human-readable description for output (optional)
        preset   -- override --policy-preset for this test only (optional)
        source   -- override --source for this test only (optional; not yet wired)

      Output format:
        PASS  [0] git ok
        FAIL  [1] rm deny
              expected: deny   got: allow  reason: (see policy log)
        [test-plan] Results: 1/2 passed  (1 FAILED)

      Suitable for committing alongside policy files and running in CI.
      A policy change that breaks an expected allow/deny is caught before
      deployment. Combine with any preset and policy layer flags:
        aishell-gate-policy --policy-preset ops_safe --test-plan tests.json
        aishell-gate-policy --policy-preset dev_sandbox --test-plan dev_tests.json

  --policy-base <path>  [ENTERPRISE]
      Path to the base policy override file.
      Default: ./aishell-gate-policy_base.json

  --policy-project <path>  [ENTERPRISE]
      Path to the project policy override file.
      Default: ./aishell-gate-policy_project.json

  --policy-user <path>
      Path to the user policy override file.
      Default: ./aishell-gate-policy_user.json

Audit options:

  --audit-log <path>
      Append a JSON Lines entry per command to the given file. In the
      Standard edition these entries are plain (no integrity chain). In the
      Enterprise edition each entry is SHA-256 hashed and linked to the
      previous entry, forming a tamper-evident chain that --audit-verify
      can validate. See AUDIT LOGGING.
      Note: there is no default path — audit logging is disabled unless this
      flag is given explicitly. This log uses a different internal format
      (entry_hash field) from the exec audit log (chain_hmac field);
      never point both programs at the same file.

  --audit-key <file>  [ENTERPRISE]
      Load a 64-byte raw binary key from file and switch audit chaining to
      HMAC-SHA256 mode. With a key, only a key-holder can forge valid chain
      hashes. Files shorter than 64 bytes are zero-padded.
      To generate a key:
        head -c 64 /dev/urandom > audit.key && chmod 640 audit.key
      WARNING: the exec audit key file uses a different format (64 ASCII hex
      characters). The two key files are NOT interchangeable.
      Requires --audit-log (or --audit-verify) to have effect.

  --audit-verify <log>  [ENTERPRISE]
      Validate the hash chain of an existing audit log file and exit. Prints
      a per-entry report to stdout. Exit codes: 0 = chain intact, 1 = chain
      break or tamper detected, 2 = file open/read error. Combine with
      --audit-key to verify an HMAC-SHA256 chain.
      Verifies policy audit logs only; do not pass an exec audit log here.

Sandbox advisory options:

  --sandbox <mode>
      Set the sandbox mode hint for the executor. Values: none, cwd_jail,
      chroot, container, userns. Default is none.
      Note: cwd_jail is the only mode actively enforced during evaluation
      (path arguments are checked against the jail root). The remaining modes
      are advisory hints recorded in the JSON output for the executor to act
      on. See SANDBOX GUIDANCE.

  --jail-root <path>
      Set the jail root path. Implies --sandbox cwd_jail. When set, path
      arguments for write-like commands must fall within this root. Uses
      a relaxed canonicalization that allows nonexistent target paths.
      The jail containment check requires that the canonicalized path begins
      with jail_root followed by '/' or end-of-string; a prefix match alone
      is insufficient (e.g. "/tmp/jail" does not contain "/tmp/jailbreak/x").

  --limit-cpu <seconds>
      Advisory CPU time limit hint for the executor. Default: 10.

  --limit-as-mb <MB>
      Advisory address space limit hint for the executor. Default: 1024.

  --limit-wall-ms <ms>
      Advisory wall clock limit hint for the executor. Default: 15000.

Input file:

  A positional argument (not starting with --) is treated as the path to
  an input file. The file may contain either a JSON envelope or a raw
  command line. If no file is given, stdin is read. There is no --in flag;
  the file path is a positional argument only.


POLICY FILES
------------

Three optional JSON override files may be layered on top of the active preset:

  aishell-gate-policy_base.json     -- base overrides       [ENTERPRISE]
  aishell-gate-policy_project.json  -- project-level overrides  [ENTERPRISE]
  aishell-gate-policy_user.json     -- per-user overrides

All three exist as a concept in both editions, but Standard edition loads
only the user-level file; the base and project files (and the corresponding
--policy-base / --policy-project flags) require Enterprise edition. Where
available, all three are optional. If a file is present but unparseable, the program
fails closed with a nonzero exit and an error message naming the offending
key or rule index. If a file is absent, defaults are used silently.

Unknown top-level keys are rejected immediately to catch typos; a key like
"cmd_denny" causes a hard error rather than being silently ignored.

Valid root keys (14 total):
  session, cmd_allow, cmd_allow_replace, cmd_deny, cmd_deny_replace,
  arg_rules, arg_rules_replace, path_rules, path_rules_replace,
  net_rules, net_rules_replace, writable_dirs, writable_dirs_replace,
  net_default_deny

A failed config load has zero effect on the running policy. The engine takes
a full snapshot of the current policy layer before applying any override;
if parsing fails at any point, the snapshot is restored and the layer is
left exactly as it was before the load attempt.

Override file format:

  {
    "cmd_allow": [
      { "pattern": "whoami",    "confirm": "none",   "io": "read", "reason": "..." },
      { "pattern": "git status","confirm": "none",                  "reason": "..." }
    ],
    "cmd_deny": [
      { "pattern": "curl",      "reason": "no network in this env" }
    ],
    "arg_rules": [
      { "cmd_pattern": "rm", "arg_glob": "-rf", "decision": "deny",
        "reason": "rm -rf denied" }
    ],
    "path_rules": [
      { "path_glob": "/tmp/*", "decision": "allow", "reason": "temp dir ok" }
    ],
    "net_rules": [
      { "host_glob": "169.254.*", "decision": "deny",
        "reason": "link-local denied" }
    ],
    "writable_dirs": [ "/tmp", "/home/user/projects" ]
  }

List behavior:
  All rule lists (cmd_allow, cmd_deny, arg_rules, path_rules, net_rules)
  append to defaults by default. To replace a list entirely, set the
  corresponding _replace flag:

    { "cmd_allow_replace": true, "cmd_allow": [ ... ] }

  Replace flags: cmd_allow_replace, cmd_deny_replace, arg_rules_replace,
  path_rules_replace, net_rules_replace.

Stack-level boolean flags:

  net_default_deny:  true|false   (optional; default true for ops_safe,
    read_only, dev_sandbox; default false for CI presets)

    When true, any command with a detected network target (URL, host:port,
    parseable address) must have an explicit net_rules allow entry for every
    target. Targets with no matching allow rule are denied, mirroring the
    command default-deny model. When false, network targets are subject only
    to explicit deny rules (cloud metadata, CGNAT, etc.).

    Set in a policy file to override the preset default:
      { "net_default_deny": false }  -- opt out for CI pipelines
      { "net_default_deny": true  }  -- enforce explicitly

    The built-in cloud metadata deny rules (169.254.*, metadata.google.internal,
    168.63.129.16, 192.0.0.192, 100.*, ::1) are unconditional and operate
    independently of net_default_deny.

Rule fields:

  cmd_allow entries:
    pattern   -- command name or "command subcommand" (required)
    confirm   -- none|plan|action|typed (optional; default none for allow)
    io        -- read|write|mixed|net|exec|unknown (optional; informational)
    reason    -- explanation string (optional)

  cmd_deny entries:
    pattern   -- command name or "command subcommand" (required)
    reason    -- explanation string (optional)

  arg_rules entries:
    cmd_pattern -- limit to this command (optional; omit for any command)
    arg_glob    -- fnmatch pattern matched against each argument (required)
    decision    -- allow|deny (optional; default deny)
    confirm     -- none|plan|action|typed (optional)
    reason      -- explanation string (optional)

  path_rules entries:
    cmd_pattern -- limit to this command (optional)
    path_glob   -- fnmatch pattern matched against canonicalized path (required)
    decision    -- allow|deny (optional; default deny)
    reason      -- explanation string (optional)

  net_rules entries:
    cmd_pattern -- limit to this command (optional)
    host_glob   -- fnmatch pattern matched against extracted hostname (required)
    port_lo     -- lower port bound, inclusive (optional; 0 means any)
    port_hi     -- upper port bound, inclusive (optional; 0 means any)
    decision    -- allow|deny (optional; default deny)
    reason      -- explanation string (optional)

  net_rules -- user policy file loopback warning:
    When the user override file (--policy-user) is loaded, net_rules
    are scanned for DECISION_ALLOW rules targeting loopback addresses
    (localhost, 127.x.x.x, ::1, 0.0.0.0, host.docker.internal). A
    warning is printed to stderr at load time. If the allowed port
    matches a known AI inference API port, the service name is included.
    Informational only -- does not block or alter the rule.

  writable_dirs:
    Array of path strings. Advisory allowlist of directories where write
    commands may operate. Used by the executor and logged in audit output.

Session overrides (within an override file):

  {
    "session": {
      "deny_ssh":       true,
      "require_tty":    false,
      "allow_modes":    ["interactive", "batch"],
      "allow_uids":     [1000, 1001],
      "deny_uids":      [],
      "allow_gids":     [],
      "deny_gids":      [],
      "allow_users":    ["alice", "bob"],
      "deny_users":     [],
      "time_window_start": "08:00",
      "time_window_end":   "18:00"
    }
  }

  time_window_start and time_window_end are UTC times in "HH:MM" format.
  Both keys must be present together -- either key alone is a parse error
  (fail closed). Time-window enforcement is enabled automatically when both
  keys are present; no separate "enabled" flag is required. A window that
  wraps midnight (start > end, e.g. "22:00" to "06:00") is supported.
  Omit both keys entirely to disable time-window enforcement.
  Maximum 16 entries per uid/gid/user list.

  Session enforcement runs before command rule evaluation. A session denied
  here is denied regardless of command allow rules.

  Unknown session keys are rejected with a parse error. A typo such as
  "deny_sssh" will not be silently ignored -- the policy file will fail
  to load and the evaluator will deny all commands.


EVALUATION ORDER
----------------

For each command, evaluation proceeds in this order:

  1. Input rejection -- immediate deny if any of the following are present:
       Shell metacharacters: |  ;  &  >  <  `  $()  ${}  &&  ||
       Command injection sequences: newline (\n), carriage return (\r)
       Non-printable C0 control bytes: 0x01-0x08, 0x0b, 0x0c, 0x0e-0x1f
       DEL character: 0x7f
     Note: tab (0x09) is treated as whitespace and is not rejected.
     Note: quoting characters (" ' `) are shell metacharacters and are
     rejected. Quote processing is not performed; quoted arguments
     containing spaces cannot be expressed. This is a security decision --
     implementing a shell grammar subset would introduce bypass surface.

  2. Command length check -- deny if longer than 4096 characters.

  3. Risk classification -- compute risk score, flags, and blast radius
     from the command catalog and argument inspection.

  4. Session enforcement -- check uid, gid, user, mode, SSH, TTY, and
     time-of-day constraints from all policy layers.

  5. Deny rules -- cmd_deny and deny arg_rules from all layers in order.

  6. Path deny rules -- for each path argument, canonicalize (allowing
     nonexistent targets) and check path_rules.

  7. Network deny rules -- for each detected host:port in arguments,
     check net_rules.

  8. Allow rules -- cmd_allow, allow arg_rules, allow path_rules, and
     allow net_rules from the highest-authority layer that produces any
     match. Layer scanning stops at that layer; lower-authority layers
     are not consulted. In default-deny mode, at least one allow rule in
     the winning layer must match the command and all path arguments.

     Net default-deny gate: if net_default_deny=true (preset default for
     ops_safe, read_only, dev_sandbox), every detected network target must
     match a net_rules allow entry. Targets with no match are denied as
     "default deny: no policy allow rule matched" -- same as a command
     with no allow rule. This check is independent of default_deny so
     CI presets can allow unrestricted network access while still enforcing
     the command allow-list.

  9. Confirmation upgrade -- if risk score >= 40, 70, or 90, confirm
     level is raised to plan, action, or typed respectively.
     Escalation is strictly monotonic; the level is never lowered.

  10. Loopback/localhost warning -- if the command targets a loopback
      address (localhost, 127.x.x.x, ::1, 0.0.0.0, or
      host.docker.internal), a graded warning is written to the
      human_extra field and included in both raw and JSON output.
      Three severity tiers:
        TIER 1  Known AI inference API port on loopback (Ollama,
                LM Studio, llama.cpp, Gradio, etc.): service named,
                AI self-access risk called out. When source=ai, the
                confirm level is escalated to CONFIRM_ACTION minimum.
        TIER 2  Loopback host, unknown port: generic localhost warning.
        TIER 3  Local alias (0.0.0.0, host.docker.internal): may
                reach local services.
      Only the highest-tier warning per evaluation is emitted.
      This step runs on the ALLOW path only, after flag_catalog_check.

  11. Taint cleared -- output marked as validated.

Layers are scanned highest-authority first. Within each layer, deny
rules are evaluated before allow rules; a deny match in the winning
layer returns immediately. Once a layer produces any match (allow or
deny), lower-authority layers are not consulted. Consequently, a deny
rule in a lower-authority layer is not overridden by a higher-layer
allow -- it is simply not read. To enforce an unoverridable deny across
a multi-layer stack, place the deny in a layer at or above every layer
that could produce a conflicting allow.


AI / JSON INTEGRATION
---------------------

aishell-gate-policy accepts structured requests and produces deterministic
JSON responses.

Each action result includes:

  decision          -- "allow" or "deny"
  confirm           -- "none", "plan", "action", or "typed"
  layer             -- policy layer that made the decision
  reason            -- human-readable explanation
  io                -- "read", "write", "mixed", "net", "exec", or "unknown"
  risk.score        -- integer 0-100
  risk.flags        -- array of flag strings
  risk.blast_radius -- "single", "tree", "system", or "unknown"
  risk.summary      -- short human-readable risk note
  busy_summary_text -- 3-5 line plain-text summary for humans or LLMs
  taint.tainted     -- bool; false only after an ALLOW decision
  taint.source      -- provenance label of the input
  argv              -- parsed argument array (ALLOW only)
  suggestions       -- allowed_commands and allowed_paths arrays (DENY only)

The envelope response also includes session metadata, policy_sources,
and an overall_decision field. As of v1.02 it also carries a protocol
version block as the first field:

  "protocol": {"name": "aishell-gate-policy-response", "version": "1.0"}

The name and version allow callers to detect schema version. Major version
bumps signal breaking changes; minor bumps are additive only.

IMPORTANT: aishell-gate-policy does not execute commands. The ALLOW output
label reads "Validated command (pass to executor)" to make this explicit.
Enforcement is performed by aishell-gate-exec(1) or another external wrapper.
This separation is intentional: the executor has no policy logic and cannot
approve or deny a command. Every decision is made by the policy engine
in a separate process; the executor reads the JSON result and acts on it.


EXECUTOR INTEGRATION
--------------------

aishell-gate-exec(1) is the companion execution harness. It accepts a JSON plan
from an AI agent, submits each action to aishell-gate-policy as a child
process, interprets the JSON evaluation result, collects human confirmation
where required, and calls execve() with the validated argv array.

It contains no policy logic. Policy decisions are made entirely by the
policy engine in a separate process.

Plan input format (stdin or --plan FILE):

  {
    "goal":     "human-readable description of intent",
    "source":   "ai" | "human" | "broker" | "scheduled"  (default: "ai"),
    "strategy": "fail_fast" | "best_effort"         (default: "fail_fast"),
    "actions":  [
      { "type": "command", "cmd": "git status" },
      { "type": "command", "cmd": "npm test" }
    ]
  }

  strategy "fail_fast"    -- stop on first non-zero exit; log skipped count
  strategy "best_effort"  -- continue through all actions

Executor-specific flags (summary):

  --policy-binary PATH      Path to this binary (aishell-gate-policy)
  --plan FILE               Read input JSON plan from FILE instead of stdin
  --eval-timeout SECS       Kill this process if evaluation exceeds SECS
  --max-response-bytes N    Kill this process if its response exceeds N bytes
  --confirm-tty             Human confirmation routing: single-session TTY
  --confirm-lock / --confirm-pipe
                            Human confirmation routing: multi-session lock
                            or FIFO relay (relay confirmation mode --
                            see aishell-gate-confirm(1))

  For the full executor flag reference, default values, and confirmation
  deployment guidance see aishell-gate-exec(1).

  FLAG SEPARATION: aishell-gate-exec and aishell-gate-policy have distinct
  flag sets. Do not pass exec flags to this binary or policy flags to exec
  directly. In particular, --audit-log on both binaries writes to DIFFERENT
  log files with incompatible formats; never point them at the same path.

Policy engine flags passed through directly (no -- separator needed):

  --policy-preset NAME      Built-in policy preset
  --policy-base FILE        Base policy override file  [ENTERPRISE]
  --policy-project FILE     Project policy override file  [ENTERPRISE]
  --policy-user FILE        User policy override file
  --jail-root PATH          Restrict write-like paths to PATH and below
  --sandbox MODE            Sandbox hint for executor

All remaining policy engine flags can be forwarded using -- as a separator:

  aishell-gate-exec [exec-flags] -- [policy-engine-flags]

Executor exit codes:

  0   All actions ALLOWed, confirmed, and executed
  1   One or more actions DENYed by policy
  2   Human confirmation refused (or no terminal available for confirmation)
  3   Policy engine process error
  4   JSON parse error (input plan or policy engine response)
  5   Usage or argument error
  6   execve() failure after confirmed ALLOW (binary not found or exec error)

Execution environment:
  The executor never inherits the caller's PATH or environment. Binaries are
  resolved against a compile-time SAFE_PATH (/usr/local/bin, /usr/bin, /bin,
  /usr/sbin, /sbin). Only an explicit allowlist of environment variables is
  propagated; all others -- including LD_PRELOAD, DYLD_INSERT_LIBRARIES,
  PYTHONPATH, and GIT_EXEC_PATH -- are silently dropped. Commands are run
  via execve() directly; no shell is invoked.

Typical integration:

    aishell-gate-exec \
        --policy-binary ./aishell-gate-policy \
        --policy-preset ops_safe \
        --audit-log ./exec.jsonl \
        --plan request.json

Remote SSH deployment with a human operator in a second session:

    # This deployment shape relies on relay confirmation mode
    # (--confirm-pipe, --confirm-lock, aishell-gate-confirm), which is
    # available in both editions. A single-session deployment can instead
    # use --confirm-tty in the same session.

    # One-time setup on the remote host (as root):
    #   groupadd aishell-gate
    #   usermod -aG aishell-gate operator
    #   usermod -aG aishell-gate ai-agent
    #   mkdir -p /run/aishell-gate
    #   chown root:aishell-gate /run/aishell-gate
    #   chmod 2770 /run/aishell-gate
    #   echo 'd /run/aishell-gate 2770 root aishell-gate -' \
    #     > /etc/tmpfiles.d/aishell-gate.conf

    # Operator's own SSH session — keep this window open:
    #   $ aishell-gate-confirm     # creates FIFOs, arms relay

    # In authorized_keys forced command for ai-agent:
    #   command="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"

    # When the AI's plan triggers a confirmation requirement, the full
    # request (cmd, goal, source, risk, reason, challenge) appears on
    # the operator's terminal via the FIFO relay.  ai-agent never opens
    # any PTY device.  Multiple concurrent AI sessions are serialised
    # through the lock; each waits its turn for the operator's attention.


SANDBOX GUIDANCE
----------------

The engine emits sandbox mode hints via --sandbox and resource limit hints
via --limit-cpu, --limit-as-mb, and --limit-wall-ms. These are advisory
outputs for a downstream executor to act on. The engine does not enforce
kernel-level containment for any mode except cwd_jail.

Sandbox modes:

  none        No sandbox hint. Default.
  cwd_jail    Path enforcement: write-like commands must target paths within
              --jail-root. This is enforced during evaluation. The executor
              receives the jail root in the JSON output for additional
              enforcement if desired.
  chroot      Advisory hint. The executor should arrange a chroot environment.
  container   Advisory hint. The executor should run the command in a
              container or namespace.
  userns      Advisory hint. The executor should use a user namespace.

Resource limit hints (all advisory; the engine does not enforce them):

  cpu_seconds   CPU time budget
  as_mb         Address space limit in megabytes
  wall_ms       Wall clock budget in milliseconds


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

TWO-LOG ARCHITECTURE
  aishell-gate uses two separate, independent audit logs:
    policy log: written by this binary via --audit-log; format identified
                by the "entry_hash" field (sentinel-substitution SHA-256 chain)
    exec log:   written by aishell-gate-exec; format identified by the
                "chain_hmac" field (HMAC-SHA256 chain)
  These logs use incompatible internal formats and are verified by different
  binaries:
    aishell-gate-policy --audit-verify FILE  (policy logs only)
    aishell-gate-exec   --audit-verify FILE  (exec logs only)
  Never point both programs at the same audit log file.
  The policy engine has NO default audit log path and NO audit-related
  environment variables. Audit logging is disabled unless --audit-log is
  given explicitly.

Using --audit-log <path> writes one JSON object per line to the given file.
The file is opened in append mode. Failure to open the file is reported
to stderr but does not affect decisions or exit status.

Each audit entry is tamper-evident: a SHA-256 hash is computed over the
entry's content (with the entry_hash field set to a 64-zero sentinel before
hashing), and each entry stores the hash of the previous entry in prev_hash.
A gap in seq values or a hash mismatch indicates deleted or modified records.

When --audit-key is provided alongside --audit-log, the chain uses
HMAC-SHA256 instead of plain SHA-256. Only a holder of the key can forge
valid chain hashes.

File locking: flock(LOCK_EX) is acquired on the file descriptor before
writing and released after flush, preventing interleaved entries from
concurrent processes. The HMAC chain anchor and sequence counter are
updated while the lock is held to prevent a forked chain from concurrent
invocations on the same file.

Each audit entry includes:

  seq                      -- monotonic sequence counter; gaps = missing entries
  session_id               -- 16 hex chars, unique per process invocation
  ts                       -- ISO 8601 UTC timestamp
  uid, gid                 -- numeric session identity
  user                     -- username from passwd database
  host                     -- hostname at invocation
  host_id                  -- same as host (schema alias)
  server_identity          -- same as host (schema alias)
  cwd                      -- working directory at invocation
  stdin_tty, is_ssh, mode  -- session context flags
  has_jail, jail_root      -- jail configuration if active
  policy_version           -- AISHELL_VERSION constant
  policy_preset            -- active preset name
  source                   -- taint source: ai|human|broker|scheduled
  input                    -- raw input string (alias: command)
  argv                     -- parsed argument array (ALLOW only)
  decision                 -- "allow" or "deny" (alias: result)
  confirm                  -- confirmation level string
  layer                    -- policy layer that fired
  reason                   -- policy reason string
  io                       -- IO classification string
  deny_kind                -- "none", "command", "arg", "path", "net",
                             "user", or "default"
  deny_detail              -- matched pattern or path that triggered deny
  risk_flags               -- pipe-separated risk flag string
  risk_score               -- integer 0-100
  blast_radius             -- blast radius string
  risk_summary             -- short risk note
  busy_summary             -- multi-line summary text
  prev_hash                -- entry_hash of the preceding record (chain link)
  chain_mode               -- "sha256" or "hmac-sha256"
  entry_hash               -- SHA-256 or HMAC-SHA256 of this record's content

Audit files should be protected by filesystem permissions. The engine
writes to the file and flushes after each entry; no rotation is performed.

To verify a log file's chain integrity:

    aishell-gate-policy --audit-verify /var/log/ag.jsonl

To verify an HMAC-SHA256 chain (requires the original key):

    aishell-gate-policy --audit-key audit.key --audit-verify /var/log/ag.jsonl

You can also verify a single entry manually:

    sed 's/"entry_hash":"[0-9a-f]\{64\}"/"entry_hash":"000...000"/' line \
      | sha256sum


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.

aishell-gate-policy (standard):
    cc -std=c11 -Wall -Wextra -O2 -o aishell-gate-policy aishell-gate-policy-110-1.c

aishell-gate-policy (enterprise):
    cc -std=c11 -Wall -Wextra -O2 -DENTERPRISE -o aishell-gate-policy aishell-gate-policy-110-1.c

aishell-gate-exec (standard):
    cc -std=c11 -Wall -Wextra -O2 -o aishell-gate-exec aishell-gate-exec-41-1.c

aishell-gate-exec (enterprise):
    cc -std=c11 -Wall -Wextra -O2 -DENTERPRISE -o aishell-gate-exec aishell-gate-exec-41-1.c

No external libraries are required by either binary. JSMN (JSON tokenizer)
and SHA-256 are embedded in both. Requires a POSIX.1-2008 compatible system
(Linux, macOS, BSDs).


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

Validate a single command interactively:

    ./aishell-gate-policy

Validate via stdin in read-only preset:

    echo "cat /etc/passwd" | ./aishell-gate-policy --policy-preset read_only

Validate a JSON action envelope:

    cat request.json | ./aishell-gate-policy --policy-preset dev_sandbox

Validate from a file with audit logging:

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

Get full JSON output for a raw command:

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

Validate with a cwd jail constraint:

    ./aishell-gate-policy --jail-root /home/user/projects

Validate with debug output:

    ./aishell-gate-policy --verbose --policy-preset read_only

Generate an HMAC key and enable authenticated audit chaining:

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

Verify an audit log's chain integrity:

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

Run a policy test suite:

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

Print policy file format reference:

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

Print version and build info:

    ./aishell-gate-policy --version

Run a multi-action plan through the executor:

    echo '{
      "goal": "update and test",
      "source": "ai",
      "strategy": "fail_fast",
      "actions": [
        {"cmd": "git pull"},
        {"cmd": "npm install"},
        {"cmd": "npm test"}
      ]
    }' | ./aishell-gate-exec --policy-binary ./aishell-gate-policy \
            --policy-preset dev_sandbox \
            --audit-log ./exec.jsonl

Run executor in best_effort mode (continue past failures):

    ./aishell-gate-exec --policy-binary ./aishell-gate-policy \
        --policy-preset ops_safe --plan request.json \
        -- --audit-log ./exec.jsonl


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

aishell-gate-policy:

  0   Evaluated without internal error (result may be ALLOW or DENY)
  1   Internal error (out of memory, policy parse failure)
  2   Usage error (unknown option, missing argument)

  For --audit-verify mode:
  0   Chain intact -- no tampering detected
  1   Chain break or tamper detected
  2   File open or read error

aishell-gate-exec exit codes are documented in aishell-gate-exec(1).


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

AIShell Labs LLC — we are an integrity company.

The two-component architecture -- separate policy engine and executor -- is
the central security property of the system, not a structural convenience.

aishell-gate-policy has no ability to execute anything. It only emits a
JSON decision record. aishell-gate-exec has no policy logic whatsoever. It cannot
approve or deny a command; it reads the decision from the policy engine process
and acts accordingly.

This separation means that a compromised executor cannot grant itself
permission to run a command that the policy engine has denied, because the
permission decision lives in an independent process that the executor has
no ability to influence. The boundary between the two components is the
JSON decision record: structured, logged, and auditable.

The two binaries serve two user populations simultaneously:

  AI agents -- the original use case. An AI generates a JSON plan; the
  executor gates it through the policy engine, collects human confirmation
  where required, and executes allowed commands.

  Human operators -- aishell-gate-policy interactive mode
  is a purely educational surface: it assesses commands, explains every
  flag with its documented reasoning, and emits a gateway echo line. It
  never executes. aishell-gate-exec interactive mode is the execution
  surface: it enforces confirmation gates and runs commands. Together they
  support a sysadmin workflow where learning and execution are distinct acts.

Integrity by design:

  The flag catalog embodies the project's integrity principle. Every
  FLAG_WARN entry carries "what the flag does; why it matters." Every
  FLAG_UNKNOWN states "risk unassessed, not evaluated as safe" -- the
  system never implies it assessed a flag and found it safe when it did
  not. The 49 commands without a flag catalog have explicit documented
  rationale for their exclusion. No entry was added or omitted silently.

aishell-gate-policy intentionally avoids:

  - Shell evaluation of any kind
  - Implicit execution
  - Hidden retries
  - Silent overrides
  - Network access
  - External library dependencies (JSMN and SHA-256 are embedded)

Explainability is a first-class feature. Every decision includes a layer,
reason, deny_kind, and flag assessment. The flag catalog notes tell the
operator exactly why a flag raises the confirmation requirement.

If uncertain, the system denies and explains. This is by design.

This tool reduces accidental damage from AI-generated commands, supports
human operator learning, and provides a tamper-evident record of every
execution decision. It is not a substitute for OS-level access control,
kernel sandboxing, or proper permission management. It complements them.


KNOWN LIMITATIONS
-----------------

Command parsing is whitespace-only. Quoted arguments containing spaces
cannot be expressed; the shell metacharacter check rejects quote characters
outright. This is a deliberate security decision: implementing a shell
grammar subset introduces ambiguity and bypass surface. Policy rules match
against naive whitespace tokens.

Network rules match argument strings, not resolved IP addresses. Rules
blocking hostnames or IP ranges can be bypassed by DNS aliases, URL encoding,
or HTTP redirects. Network rules provide best-effort intent capture, not
strong enforcement.

The JSON token limit is 4096 tokens per parse. Very large policy files or
request envelopes may be rejected. The error message will indicate a parse
failure; reduce the size of the input.

Path rules call a relaxed canonicalization function that resolves existing
path components and allows nonexistent leaf components. Symlinks within the
existing portion of a path are resolved; symlinks in nonexistent components
are not checked.

Time-window enforcement uses hour granularity. Minutes in time_window_start
and time_window_end are parsed and validated but not used in enforcement.

Maximum 16 entries per uid, gid, or user list in session policy.
Maximum 63 arguments per command.
Maximum 8 allowed_commands and 8 allowed_paths in suggestion output.


SEE ALSO
--------

aishell-gate-exec(1)   -- companion execution harness; see aishell-gate-exec(1)
aishell-gate-mcp(1)    -- MCP server; exposes evaluate_plan and execute_plan tools
aishell-gate-confirm(1)     -- operator confirmation relay; creates FIFOs for --confirm-pipe
fnmatch(3)        -- glob pattern matching used by rule evaluation
flock(2)          -- advisory locking used by the audit subsystem and confirm lock
execve(2)         -- system call used by the executor for command execution
sha256(3)         -- hash function used for audit chain integrity



VERSION
-------

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

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


REVISION HISTORY (manpage)
--------------------------

v111  Corrections to match the shipped binaries. --test-plan tagged
      [ENTERPRISE] in OPTIONS (it is edition-gated in policy v1.16 and was
      the last untagged Enterprise flag). --source value list corrected
      throughout to ai|human|broker|scheduled (default ai), with
      raw|envelope|web noted as legacy aliases -- the old list
      (raw|envelope|ai|web|unknown, default raw) predated the caller-
      identity rename. --audit-log wording clarified: the Standard log is
      plain JSON Lines; the SHA-256/HMAC tamper-evident chain and
      --audit-verify are Enterprise. Companion relay tool renamed
      aishell-gate-confirm -> aishell-gate-confirm to match the shipped
      filename. Contact address corrected to info@aishellgate.com. No
      code change; documentation only.

v110  Relay confirmation mode reverted to Standard (permanent), reversing
      the v109 tagging below. --confirm-lock, --confirm-pipe, and
      aishell-gate-confirm are available in both editions: the MCP server ships
      Standard-only and depends on --confirm-pipe, so relay mode cannot move
      to Enterprise without breaking it. [ENTERPRISE] tags removed from the
      executor-flags summary, the remote SSH deployment example (and its
      explanatory comment), and the aishell-gate-confirm(1) SEE ALSO entry. The
      --policy-base / --policy-project tagging from v109 is unaffected and
      remains [ENTERPRISE]. --confirm-tty unchanged (Standard). Also tagged
      --audit-key [ENTERPRISE] in OPTIONS -- a pre-existing gap (it is
      edition-gated in the binary and its sibling --audit-verify was already
      tagged, but --audit-key itself was not). No code change; documentation
      only.

v109  Relay confirmation mode marked [ENTERPRISE], per the decision that
      --confirm-lock, --confirm-pipe, and aishell-gate-confirm are Enterprise-
      only (--confirm-tty remains Standard). Updated in the executor-flags
      summary, the pass-through policy-flags summary (which also had
      --policy-base / --policy-project untagged there -- missed in v108,
      fixed here), the remote SSH deployment example, and the SEE ALSO
      entry for aishell-gate-confirm(1). No code change; documentation only.

v108  Edition split corrected to match current licensing decisions.
      --dump-policy removed entirely: no longer documented in EDITIONS,
      SYNOPSIS, or OPTIONS, under any edition. The flag remains in the
      aishell-gate-policy C source but is undocumented and for internal
      AIShell Labs use only. ci_build, ci_deploy, ci_admin, and danger_zone
      are now marked [ENTERPRISE] in EDITIONS, PRESET MODES, and
      --policy-preset. --policy-base and --policy-project (and the
      corresponding aishell-gate-policy_base.json / _project.json files)
      are now marked [ENTERPRISE] in EDITIONS, OPTIONS, POLICY FILES, and
      the "Policies" concept section; --policy-user remains available in
      both editions. No code change; documentation only.

v107  Policy layer precedence corrected throughout. Earlier versions
      described the allow-phase merge as "all layers scanned; OR of all
      matches; MAX of all matching confirms." The actual engine scans
      layers highest-authority first and stops at the first layer that
      produces any match. The description in "Policies", evaluation
      step 8, and the trailing summary paragraph have been rewritten to
      match this behaviour. No code change; documentation only.

v106  Previous release.
