cli-guard examples/policy

NAME

policy-demo - show shell-metacharacter argv rejection

SYNOPSIS

policy-demo

DESCRIPTION

policy-demo exercises cli-guard's argv pre-validation gate. Every call into a wrapped Action passes through policy.ValidateArg before execve. The gate rejects any string containing one of these bytes:

`  $  ;  &  |  <  >  (  )  {  }  \  \n  \r  \t

Why this exists, even though cli-guard always builds an explicit argv slice and never invokes /bin/sh: a non-trivial fraction of downstream tools hand their last positional argument to a remote shell. Examples:

ssh user@host '<remote-command>'
kubectl exec pod -- sh -c '<command>'
git config --global core.editor '<editor-cmd>'

If the agent driving cli-guard never sanitizes inputs and the wrapped tool unsplats argv into a shell on the other side, a single semicolon in an argument turns a benign verb into a chained injection. Rejecting the metacharacters at the coily boundary keeps that one-layer leak from becoming an execution surprise two hops downstream.

Operating model for an agent calling these commands:

- Any rejected input fails the verb deterministically with
  exitcode.PolicyDenied (2). The argv never reaches execve.
- The error format is stable and parseable:
    policy: shell metacharacter rejected: arg <name> contains '<char>' at index <i>
- On rejection, DO NOT retry with a quoted or escaped variant. The
  input is hostile by definition. Surface the rejection to the
  operator.
- The gate is content-only. It does not check semantics. "rm -rf /"
  with no metacharacters passes the gate; whether the wrapped tool
  should run it is a separate verb-allowlist decision.

The two leaves below share an identical Action body. The only difference is the input the operator types. That is the point: the gate is the gate, not the leaf.

Usage:

policy-demo [GLOBAL OPTIONS] [command [COMMAND OPTIONS]] [ARGUMENTS...]

COMMANDS

safe

validates a single positional arg

unsafe

demonstrate the rejection path